summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:26:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:26:00 +0000
commit830407e88f9d40d954356c3754f2647f91d5c06a (patch)
treed6a0ece6feea91f3c656166dbaa884ef8a29740e /modules
parentInitial commit. (diff)
downloadknot-resolver-830407e88f9d40d954356c3754f2647f91d5c06a.tar.xz
knot-resolver-830407e88f9d40d954356c3754f2647f91d5c06a.zip
Adding upstream version 5.6.0.upstream/5.6.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules')
-rw-r--r--modules/README.rst251
-rw-r--r--modules/bogus_log/.packaging/test.config4
-rw-r--r--modules/bogus_log/README.rst45
-rw-r--r--modules/bogus_log/bogus_log.c135
-rw-r--r--modules/bogus_log/meson.build21
-rw-r--r--modules/bogus_log/test.integr/deckard.yaml13
-rw-r--r--modules/bogus_log/test.integr/kresd_config.j290
-rw-r--r--modules/bogus_log/test.integr/val_minimal_expiredsignature.rpl125
-rw-r--r--modules/cookies/README.rst56
-rw-r--r--modules/cookies/cookiectl.c689
-rw-r--r--modules/cookies/cookiectl.h35
-rw-r--r--modules/cookies/cookiemonster.c464
-rw-r--r--modules/cookies/cookiemonster.h15
-rw-r--r--modules/cookies/cookies.c78
-rw-r--r--modules/daf/.packaging/test.config4
-rw-r--r--modules/daf/README.rst146
-rw-r--r--modules/daf/daf.js295
-rw-r--r--modules/daf/daf.lua392
-rw-r--r--modules/daf/daf.test.lua80
-rw-r--r--modules/daf/daf_http.test.lua216
-rw-r--r--modules/daf/meson.build21
-rw-r--r--modules/daf/test.integr/deckard.yaml12
-rw-r--r--modules/daf/test.integr/kresd_config.j265
-rw-r--r--modules/daf/test.integr/module_daf.rpl30
-rw-r--r--modules/detect_time_jump/.packaging/test.config4
-rw-r--r--modules/detect_time_jump/README.rst22
-rw-r--r--modules/detect_time_jump/detect_time_jump.lua45
-rw-r--r--modules/detect_time_skew/.packaging/test.config4
-rw-r--r--modules/detect_time_skew/README.rst23
-rw-r--r--modules/detect_time_skew/detect_time_skew.lua83
-rw-r--r--modules/dns64/.packaging/test.config4
-rw-r--r--modules/dns64/README.rst62
-rw-r--r--modules/dns64/dns64.lua220
-rw-r--r--modules/dns64/dns64.test.lua53
-rw-r--r--modules/dnstap/.packaging/centos/7/builddeps3
-rw-r--r--modules/dnstap/.packaging/centos/7/rundeps2
-rw-r--r--modules/dnstap/.packaging/centos/8/builddeps3
-rw-r--r--modules/dnstap/.packaging/centos/8/rundeps2
-rw-r--r--modules/dnstap/.packaging/debian/10/builddeps3
-rw-r--r--modules/dnstap/.packaging/debian/10/rundeps2
-rw-r--r--modules/dnstap/.packaging/debian/9/builddeps3
-rw-r--r--modules/dnstap/.packaging/debian/9/rundeps2
-rw-r--r--modules/dnstap/.packaging/fedora/31/builddeps3
-rw-r--r--modules/dnstap/.packaging/fedora/31/rundeps2
-rw-r--r--modules/dnstap/.packaging/fedora/32/builddeps3
-rw-r--r--modules/dnstap/.packaging/fedora/32/rundeps2
-rw-r--r--modules/dnstap/.packaging/leap/15.2/builddeps3
-rw-r--r--modules/dnstap/.packaging/leap/15.2/rundeps2
-rw-r--r--modules/dnstap/.packaging/test.config4
-rw-r--r--modules/dnstap/.packaging/ubuntu/16.04/builddeps3
-rw-r--r--modules/dnstap/.packaging/ubuntu/16.04/rundeps2
-rw-r--r--modules/dnstap/.packaging/ubuntu/18.04/builddeps3
-rw-r--r--modules/dnstap/.packaging/ubuntu/18.04/rundeps2
-rw-r--r--modules/dnstap/.packaging/ubuntu/20.04/builddeps3
-rw-r--r--modules/dnstap/.packaging/ubuntu/20.04/rundeps2
-rw-r--r--modules/dnstap/README.rst42
-rw-r--r--modules/dnstap/dnstap.c524
-rw-r--r--modules/dnstap/dnstap.proto273
-rw-r--r--modules/dnstap/meson.build57
-rw-r--r--modules/edns_keepalive/.packaging/test.config10
-rw-r--r--modules/edns_keepalive/README.rst22
-rw-r--r--modules/edns_keepalive/edns_keepalive.c61
-rw-r--r--modules/edns_keepalive/meson.build17
-rwxr-xr-xmodules/etcd/.packaging/centos/7/pre-test.sh1
-rw-r--r--modules/etcd/.packaging/centos/7/rundeps6
-rw-r--r--modules/etcd/.packaging/centos/8/NOTSUPPORTED0
-rwxr-xr-xmodules/etcd/.packaging/debian/10/pre-test.sh1
-rw-r--r--modules/etcd/.packaging/debian/10/rundeps4
-rwxr-xr-xmodules/etcd/.packaging/debian/9/pre-test.sh1
-rw-r--r--modules/etcd/.packaging/debian/9/rundeps4
-rw-r--r--modules/etcd/.packaging/fedora/31/NOTSUPPORTED16
-rw-r--r--modules/etcd/.packaging/fedora/32/NOTSUPPORTED16
-rwxr-xr-xmodules/etcd/.packaging/leap/15.2/pre-test.sh1
-rw-r--r--modules/etcd/.packaging/leap/15.2/rundeps6
-rw-r--r--modules/etcd/.packaging/test.config4
-rwxr-xr-xmodules/etcd/.packaging/ubuntu/16.04/pre-test.sh1
-rw-r--r--modules/etcd/.packaging/ubuntu/16.04/rundeps3
-rwxr-xr-xmodules/etcd/.packaging/ubuntu/18.04/pre-test.sh1
-rw-r--r--modules/etcd/.packaging/ubuntu/18.04/rundeps3
-rwxr-xr-xmodules/etcd/.packaging/ubuntu/20.04/pre-test.sh1
-rw-r--r--modules/etcd/.packaging/ubuntu/20.04/rundeps4
-rw-r--r--modules/etcd/README.rst46
-rw-r--r--modules/etcd/etcd.lua56
-rw-r--r--modules/experimental_dot_auth/.packaging/centos/7/rundeps1
-rw-r--r--modules/experimental_dot_auth/.packaging/centos/8/rundeps1
-rw-r--r--modules/experimental_dot_auth/.packaging/debian/10/rundeps1
-rw-r--r--modules/experimental_dot_auth/.packaging/debian/9/rundeps1
-rw-r--r--modules/experimental_dot_auth/.packaging/fedora/31/rundeps1
-rw-r--r--modules/experimental_dot_auth/.packaging/fedora/32/rundeps1
-rw-r--r--modules/experimental_dot_auth/.packaging/leap/15.2/NOTSUPPORTED6
-rwxr-xr-xmodules/experimental_dot_auth/.packaging/leap/15.2/pre-test.sh1
-rw-r--r--modules/experimental_dot_auth/.packaging/leap/15.2/rundeps4
-rw-r--r--modules/experimental_dot_auth/.packaging/test.config4
-rw-r--r--modules/experimental_dot_auth/.packaging/ubuntu/16.04/NOTSUPPORTED0
-rw-r--r--modules/experimental_dot_auth/.packaging/ubuntu/18.04/rundeps1
-rw-r--r--modules/experimental_dot_auth/.packaging/ubuntu/20.04/rundeps1
-rw-r--r--modules/experimental_dot_auth/README.rst91
-rw-r--r--modules/experimental_dot_auth/experimental_dot_auth.lua122
-rw-r--r--modules/experimental_dot_auth/meson.build13
-rw-r--r--modules/extended_error/extended_error.c47
-rw-r--r--modules/extended_error/meson.build20
-rw-r--r--modules/graphite/.packaging/centos/7/rundeps1
-rw-r--r--modules/graphite/.packaging/centos/8/rundeps1
-rw-r--r--modules/graphite/.packaging/debian/10/rundeps1
-rw-r--r--modules/graphite/.packaging/debian/9/rundeps1
-rw-r--r--modules/graphite/.packaging/fedora/31/rundeps1
-rw-r--r--modules/graphite/.packaging/fedora/32/rundeps1
-rw-r--r--modules/graphite/.packaging/leap/15.2/NOTSUPPORTED6
-rwxr-xr-xmodules/graphite/.packaging/leap/15.2/pre-test.sh1
-rw-r--r--modules/graphite/.packaging/leap/15.2/rundeps6
-rw-r--r--modules/graphite/.packaging/test.config4
-rw-r--r--modules/graphite/.packaging/ubuntu/16.04/rundeps1
-rw-r--r--modules/graphite/.packaging/ubuntu/18.04/rundeps1
-rw-r--r--modules/graphite/.packaging/ubuntu/20.04/rundeps1
-rw-r--r--modules/graphite/README.rst49
-rw-r--r--modules/graphite/graphite.lua146
-rw-r--r--modules/hints/.packaging/test.config4
-rw-r--r--modules/hints/README.rst145
-rw-r--r--modules/hints/hints.c677
-rw-r--r--modules/hints/meson.build24
-rw-r--r--modules/hints/tests/hints.test.hosts1
-rw-r--r--modules/hints/tests/hints.test.lua64
-rw-r--r--modules/hints/tests/hints_test.zone2
-rw-r--r--modules/http/.packaging/centos/7/rundeps1
-rw-r--r--modules/http/.packaging/centos/8/rundeps1
-rw-r--r--modules/http/.packaging/debian/10/rundeps1
-rw-r--r--modules/http/.packaging/debian/9/rundeps1
-rw-r--r--modules/http/.packaging/fedora/31/rundeps1
-rw-r--r--modules/http/.packaging/fedora/32/rundeps1
-rw-r--r--modules/http/.packaging/leap/15.2/NOTSUPPORTED5
-rwxr-xr-xmodules/http/.packaging/leap/15.2/pre-test.sh1
-rw-r--r--modules/http/.packaging/leap/15.2/rundeps7
-rw-r--r--modules/http/.packaging/test.config4
-rw-r--r--modules/http/.packaging/ubuntu/16.04/NOTSUPPORTED0
-rw-r--r--modules/http/.packaging/ubuntu/18.04/rundeps1
-rw-r--r--modules/http/.packaging/ubuntu/20.04/rundeps1
-rw-r--r--modules/http/README.rst188
-rw-r--r--modules/http/custom_services.rst145
-rw-r--r--modules/http/debug_opensslkeylog.c369
-rw-r--r--modules/http/http.lua.in418
-rw-r--r--modules/http/http.test.lua128
-rw-r--r--modules/http/http_doh.lua116
-rw-r--r--modules/http/http_doh.test.lua419
-rw-r--r--modules/http/http_tls_cert.lua186
-rw-r--r--modules/http/http_trace.lua77
-rw-r--r--modules/http/meson.build61
-rw-r--r--modules/http/prometheus.lua178
-rw-r--r--modules/http/prometheus.rst45
-rw-r--r--modules/http/static/bootstrap-theme.min.css6
-rw-r--r--modules/http/static/bootstrap-theme.min.css.spdx11
-rw-r--r--modules/http/static/bootstrap.min.css11
-rw-r--r--modules/http/static/bootstrap.min.css.spdx11
-rw-r--r--modules/http/static/bootstrap.min.js7
-rw-r--r--modules/http/static/bootstrap.min.js.spdx11
-rw-r--r--modules/http/static/d3.js6
-rw-r--r--modules/http/static/d3.spdx12
-rw-r--r--modules/http/static/datamaps.world.min.js3
-rw-r--r--modules/http/static/datamaps.world.min.spdx11
-rw-r--r--modules/http/static/dygraph.min.js7
-rw-r--r--modules/http/static/dygraph.min.js.spdx12
-rw-r--r--modules/http/static/epoch.css2
-rw-r--r--modules/http/static/epoch.js4
-rw-r--r--modules/http/static/epoch.spdx11
-rw-r--r--modules/http/static/favicon.icobin0 -> 1545 bytes
-rw-r--r--modules/http/static/glyphicons-halflings-regular.spdx11
-rw-r--r--modules/http/static/glyphicons-halflings-regular.woff2bin0 -> 18028 bytes
-rw-r--r--modules/http/static/jquery.js5
-rw-r--r--modules/http/static/jquery.spdx12
-rw-r--r--modules/http/static/kresd.css44
-rw-r--r--modules/http/static/kresd.js367
-rw-r--r--modules/http/static/main.tpl87
-rw-r--r--modules/http/static/selectize.bootstrap3.css418
-rw-r--r--modules/http/static/selectize.min.js4
-rw-r--r--modules/http/static/selectize.spdx11
-rw-r--r--modules/http/static/topojson.js2
-rw-r--r--modules/http/static/topojson.spdx12
-rw-r--r--modules/http/test_tls/broken.crt3
-rw-r--r--modules/http/test_tls/broken.keybin0 -> 512 bytes
-rw-r--r--modules/http/test_tls/ca.crt20
-rw-r--r--modules/http/test_tls/chain.crt41
-rw-r--r--modules/http/test_tls/test.crt20
-rw-r--r--modules/http/test_tls/test.key27
-rw-r--r--modules/http/test_tls/tls.test.lua193
-rw-r--r--modules/http/trace.rst43
-rw-r--r--modules/meson.build59
-rw-r--r--modules/nsid/.packaging/test.config4
-rw-r--r--modules/nsid/README.rst35
-rw-r--r--modules/nsid/meson.build24
-rw-r--r--modules/nsid/nsid.c114
-rw-r--r--modules/nsid/nsid.test.lua22
-rw-r--r--modules/policy/.packaging/test.config4
-rw-r--r--modules/policy/README.rst774
-rw-r--r--modules/policy/lua-aho-corasick/LICENSE28
-rw-r--r--modules/policy/lua-aho-corasick/Makefile134
-rw-r--r--modules/policy/lua-aho-corasick/README.md40
-rw-r--r--modules/policy/lua-aho-corasick/ac.cxx101
-rw-r--r--modules/policy/lua-aho-corasick/ac.h49
-rw-r--r--modules/policy/lua-aho-corasick/ac_fast.cxx468
-rw-r--r--modules/policy/lua-aho-corasick/ac_fast.hpp124
-rw-r--r--modules/policy/lua-aho-corasick/ac_lua.cxx173
-rw-r--r--modules/policy/lua-aho-corasick/ac_slow.cxx318
-rw-r--r--modules/policy/lua-aho-corasick/ac_slow.hpp158
-rw-r--r--modules/policy/lua-aho-corasick/ac_util.hpp69
-rw-r--r--modules/policy/lua-aho-corasick/load_ac.lua90
-rw-r--r--modules/policy/lua-aho-corasick/mytest.cxx200
-rw-r--r--modules/policy/lua-aho-corasick/tests/Makefile65
-rw-r--r--modules/policy/lua-aho-corasick/tests/ac_bench.cxx519
-rw-r--r--modules/policy/lua-aho-corasick/tests/ac_test_aggr.cxx135
-rw-r--r--modules/policy/lua-aho-corasick/tests/ac_test_simple.cxx275
-rw-r--r--modules/policy/lua-aho-corasick/tests/dict/README.txt1
-rw-r--r--modules/policy/lua-aho-corasick/tests/dict/dict1.txt11
-rw-r--r--modules/policy/lua-aho-corasick/tests/load_ac_test.lua82
-rw-r--r--modules/policy/lua-aho-corasick/tests/lua_test.lua67
-rw-r--r--modules/policy/lua-aho-corasick/tests/test_base.hpp60
-rw-r--r--modules/policy/lua-aho-corasick/tests/test_bigfile.cxx167
-rw-r--r--modules/policy/lua-aho-corasick/tests/test_main.cxx33
-rw-r--r--modules/policy/meson.build50
-rw-r--r--modules/policy/noipv6.test.integr/broken-ipv6.rpl47
-rw-r--r--modules/policy/noipv6.test.integr/deckard.yaml12
-rw-r--r--modules/policy/noipv6.test.integr/kresd_config.j259
-rw-r--r--modules/policy/noipvx.test.integr/broken-ipvx.rpl35
-rw-r--r--modules/policy/noipvx.test.integr/deckard.yaml12
-rw-r--r--modules/policy/noipvx.test.integr/kresd_config.j260
-rw-r--r--modules/policy/policy.lua1109
-rw-r--r--modules/policy/policy.rpz.test.lua65
-rw-r--r--modules/policy/policy.slice.test.lua109
-rw-r--r--modules/policy/policy.test.lua145
-rw-r--r--modules/policy/policy.test.rpz18
-rw-r--r--modules/policy/policy.test.rpz.soa5
-rw-r--r--modules/policy/test.integr/deckard.yaml12
-rw-r--r--modules/policy/test.integr/kresd_config.j259
-rw-r--r--modules/policy/test.integr/refuse.rpl44
-rw-r--r--modules/predict/.packaging/test.config4
-rw-r--r--modules/predict/README.rst67
-rw-r--r--modules/predict/predict.lua189
-rw-r--r--modules/predict/predict.test.lua61
-rw-r--r--modules/prefill/.packaging/test.config4
-rw-r--r--modules/prefill/README.rst43
-rw-r--r--modules/prefill/prefill.lua199
-rw-r--r--modules/prefill/prefill.test/empty.zone0
-rw-r--r--modules/prefill/prefill.test/example.com.zone12
-rw-r--r--modules/prefill/prefill.test/prefill.test.lua123
-rw-r--r--modules/prefill/prefill.test/random.zone2
-rw-r--r--modules/prefill/prefill.test/testroot.zone59
-rw-r--r--modules/prefill/prefill.test/testroot.zone.unsigned4
-rw-r--r--modules/prefill/prefill.test/testroot_no_soa.zone52
-rw-r--r--modules/priming/.packaging/test.config4
-rw-r--r--modules/priming/README.rst18
-rw-r--r--modules/priming/priming.lua130
-rw-r--r--modules/rebinding/.packaging/test.config4
-rw-r--r--modules/rebinding/README.rst29
-rw-r--r--modules/rebinding/rebinding.lua115
-rw-r--r--modules/rebinding/test.integr/deckard.yaml12
-rw-r--r--modules/rebinding/test.integr/kresd_config.j259
-rw-r--r--modules/rebinding/test.integr/module_rebinding.rpl834
-rw-r--r--modules/refuse_nord/.packaging/test.config3
-rw-r--r--modules/refuse_nord/README.rst16
-rw-r--r--modules/refuse_nord/meson.build21
-rw-r--r--modules/refuse_nord/refuse_nord.c38
-rw-r--r--modules/refuse_nord/test.integr/deckard.yaml12
-rw-r--r--modules/refuse_nord/test.integr/kresd_config.j256
-rw-r--r--modules/refuse_nord/test.integr/refuse_nord.rpl24
-rw-r--r--modules/renumber/.packaging/test.config4
-rw-r--r--modules/renumber/README.rst36
-rw-r--r--modules/renumber/renumber.lua181
-rw-r--r--modules/renumber/renumber.test.lua103
-rw-r--r--modules/rfc7706.rst12
-rw-r--r--modules/serve_stale/.packaging/test.config4
-rw-r--r--modules/serve_stale/README.rst23
-rw-r--r--modules/serve_stale/serve_stale.lua42
-rw-r--r--modules/serve_stale/test.integr/deckard.yaml12
-rw-r--r--modules/serve_stale/test.integr/kresd_config.j270
-rw-r--r--modules/serve_stale/test.integr/module_serve_stale.rpl280
-rw-r--r--modules/stats/.packaging/test.config4
-rw-r--r--modules/stats/README.rst211
-rw-r--r--modules/stats/meson.build25
-rw-r--r--modules/stats/stats.c534
-rw-r--r--modules/stats/test.integr/deckard.yaml12
-rw-r--r--modules/stats/test.integr/kresd_config.j2114
-rw-r--r--modules/stats/test.integr/stats.rpl194
-rw-r--r--modules/ta_sentinel/.packaging/test.config4
-rw-r--r--modules/ta_sentinel/README.rst18
-rw-r--r--modules/ta_sentinel/ta_sentinel.lua80
-rw-r--r--modules/ta_signal_query/.packaging/test.config4
-rw-r--r--modules/ta_signal_query/README.rst31
-rw-r--r--modules/ta_signal_query/ta_signal_query.lua64
-rw-r--r--modules/ta_update/.packaging/test.config4
-rw-r--r--modules/ta_update/meson.build21
-rw-r--r--modules/ta_update/root.keys1
-rw-r--r--modules/ta_update/ta_update.lua349
-rw-r--r--modules/ta_update/ta_update.test.integr/deckard.yaml12
-rw-r--r--modules/ta_update/ta_update.test.integr/kresd_config.j256
-rw-r--r--modules/ta_update/ta_update.test.integr/rfc5011-monotonictime.rpl5755
-rw-r--r--modules/ta_update/ta_update.test.integr/rfc5011/README13
-rwxr-xr-xmodules/ta_update/ta_update.test.integr/rfc5011/dns2rpl.py222
-rw-r--r--modules/ta_update/ta_update.test.integr/rfc5011/empty.rpl20
-rwxr-xr-xmodules/ta_update/ta_update.test.integr/rfc5011/genkeyszones.sh174
-rw-r--r--modules/ta_update/ta_update.test.integr/rfc5011/knot.root.conf26
l---------modules/ta_update/ta_update.test.integr/rfc5011/pydnstest1
-rw-r--r--modules/ta_update/ta_update.test.integr/rfc5011/unsigned_check.db8
-rw-r--r--modules/ta_update/ta_update.test.integr/rfc5011/unsigned_ok.db8
-rw-r--r--modules/ta_update/ta_update.test.integr/rfc5011_unsupported_key_rollover.rpl91
-rw-r--r--modules/ta_update/ta_update.test.lua84
-rw-r--r--modules/ta_update/ta_update.unmanagedkey.test.integr/deckard.yaml12
-rw-r--r--modules/ta_update/ta_update.unmanagedkey.test.integr/kresd_config.j272
l---------modules/ta_update/ta_update.unmanagedkey.test.integr/rfc50111
-rw-r--r--modules/ta_update/ta_update.unmanagedkey.test.integr/unmanagedkey-missing-monotonictime.rpl758
-rw-r--r--modules/ta_update/ta_update.unmanagedkey.test.integr/unmanagedkey-present-monotonictime.rpl757
-rw-r--r--modules/ta_update/ta_update.unmanagedkey.test.integr/unmanagedkey-revoke-monotonictime.rpl762
-rw-r--r--modules/view/.packaging/test.config4
-rw-r--r--modules/view/README.rst92
-rw-r--r--modules/view/addr.test.integr/deckard.yaml12
-rw-r--r--modules/view/addr.test.integr/kresd_config.j262
-rw-r--r--modules/view/addr.test.integr/module_view_addr.rpl79
-rw-r--r--modules/view/meson.build11
-rw-r--r--modules/view/tsig.test.integr/deckard.yaml12
-rw-r--r--modules/view/tsig.test.integr/kresd_config.j264
-rw-r--r--modules/view/tsig.test.integr/module_view_tsig.rpl114
-rw-r--r--modules/view/view.lua121
-rw-r--r--modules/watchdog/.packaging/test.config4
-rw-r--r--modules/watchdog/README.rst43
-rw-r--r--modules/watchdog/watchdog.lua129
-rw-r--r--modules/workarounds/.packaging/test.config4
-rw-r--r--modules/workarounds/README.rst11
-rw-r--r--modules/workarounds/workarounds.lua23
325 files changed, 30783 insertions, 0 deletions
diff --git a/modules/README.rst b/modules/README.rst
new file mode 100644
index 0000000..1096c37
--- /dev/null
+++ b/modules/README.rst
@@ -0,0 +1,251 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _modules-api:
+
+*********************
+Modules API reference
+*********************
+
+.. contents::
+ :depth: 1
+ :local:
+
+Supported languages
+===================
+
+Currently modules written in C and Lua(JIT) are supported.
+
+The anatomy of an extension
+===========================
+
+A module is a shared object or script defining specific functions/fields; here's an overview.
+
+.. csv-table::
+ :header: "C", "Lua", "Params", "Comment"
+
+ "``X_api()`` [#]_", "", "", "API version"
+ "``X_init()``", "``X.init()``", "``module``", "Constructor"
+ "``X_deinit()``", "``X.deinit()``", "``module``", "Destructor"
+ "``X_config()``", "``X.config()``", "``module, str``", "Configuration"
+ "``X_layer``", "``X.layer``", "", ":ref:`Module layer <lib-layers>`"
+ "``X_props``", "", "", "List of properties"
+
+.. [#] Mandatory symbol; defined by using :c:func:`KR_MODULE_EXPORT`.
+
+The ``X`` corresponds to the module name; if the module name is ``hints``, the prefix for constructor would be ``hints_init()``.
+More details are in docs for the :c:type:`kr_module` and :c:type:`kr_layer_api` structures.
+
+.. note::
+ The modules get ordered -- by default in the same as the order in which they were loaded. The loading command can specify where in the order the module should be positioned.
+
+
+Writing a module in Lua
+=======================
+
+The probably most convenient way of writing modules is Lua since you can use already installed modules
+from system and have first-class access to the scripting engine. You can also tap to all the events, that
+the C API has access to, but keep in mind that transitioning from the C to Lua function is slower than
+the other way round, especially when JIT-compilation is taken into account.
+
+.. note:: The Lua functions retrieve an additional first parameter compared to the C counterparts - a "state".
+ Most useful C functions and structures have lua FFI wrappers, sometimes with extra sugar.
+
+The modules follow the `Lua way <http://lua-users.org/wiki/ModuleDefinition>`_, where the module interface is returned in a named table.
+
+.. code-block:: lua
+
+ --- @module Count incoming queries
+ local counter = {}
+
+ function counter.init(module)
+ counter.total = 0
+ counter.last = 0
+ counter.failed = 0
+ end
+
+ function counter.deinit(module)
+ print('counted', counter.total, 'queries')
+ end
+
+ -- @function Run the q/s counter with given interval.
+ function counter.config(conf)
+ -- We can use the scripting facilities here
+ if counter.ev then event.cancel(counter.ev)
+ event.recurrent(conf.interval, function ()
+ print(counter.total - counter.last, 'q/s')
+ counter.last = counter.total
+ end)
+ end
+
+ return counter
+
+.. vv Hmm, we do not use these coroutine returns anywhere, so it's unclear whether they still work OK. Splitting work over time is now typically done via the ``event`` timers.
+
+.. The API functions may return an integer value just like in other languages, but they may also return a coroutine that will be continued asynchronously. A good use case for this approach is is a deferred initialization, e.g. loading a chunks of data or waiting for I/O.
+
+.. .. code-block:: lua
+
+ function counter.init(module)
+ counter.total = 0
+ counter.last = 0
+ counter.failed = 0
+ return coroutine.create(function ()
+ for line in io.lines('/etc/hosts') do
+ load(module, line)
+ coroutine.yield()
+ end
+ end)
+ end
+
+The created module can be then loaded just like any other module, except it isn't very useful since it
+doesn't provide any layer to capture events. The Lua module can however provide a processing layer, just
+:ref:`like its C counterpart <lib-layers>`.
+
+.. code-block:: lua
+
+ -- Notice it isn't a function, but a table of functions
+ counter.layer = {
+ begin = function (state, data)
+ counter.total = counter.total + 1
+ return state
+ end,
+ finish = function (state, req, answer)
+ if state == kres.FAIL then
+ counter.failed = counter.failed + 1
+ end
+ return state
+ end
+ }
+
+There is currently an additional "feature" in comparison to C layer functions:
+some functions do not get called at all if ``state == kres.FAIL``;
+see docs for details: :c:type:`kr_layer_api`.
+
+Since the modules are like any other Lua modules, you can interact with them through the CLI and and any interface.
+
+.. tip:: Module discovery: ``kres_modules.`` is prepended to the module name and lua search path is used on that.
+
+
+Writing a module in C
+=====================
+
+As almost all the functions are optional, the minimal module looks like this:
+
+.. code-block:: c
+
+ #include "lib/module.h"
+ /* Convenience macro to declare module ABI. */
+ KR_MODULE_EXPORT(mymodule)
+
+.. TODO it's probably not a good idea to start C module tutorial by pthread_create()
+
+Let's define an observer thread for the module as well. It's going to be stub for the sake of brevity,
+but you can for example create a condition, and notify the thread from query processing by declaring
+module layer (see the :ref:`Writing layers <lib-layers>`).
+
+.. code-block:: c
+
+ static void* observe(void *arg)
+ {
+ /* ... do some observing ... */
+ }
+
+ int mymodule_init(struct kr_module *module)
+ {
+ /* Create a thread and start it in the background. */
+ pthread_t thr_id;
+ int ret = pthread_create(&thr_id, NULL, &observe, NULL);
+ if (ret != 0) {
+ return kr_error(errno);
+ }
+
+ /* Keep it in the thread */
+ module->data = thr_id;
+ return kr_ok();
+ }
+
+ int mymodule_deinit(struct kr_module *module)
+ {
+ /* ... signalize cancellation ... */
+ void *res = NULL;
+ pthread_t thr_id = (pthread_t) module->data;
+ int ret = pthread_join(thr_id, res);
+ if (ret != 0) {
+ return kr_error(errno);
+ }
+
+ return kr_ok();
+ }
+
+This example shows how a module can run in the background, this enables you to, for example, observe
+and publish data about query resolution.
+
+Configuring modules
+===================
+
+There is a callback ``X_config()`` that you can implement, see hints module.
+
+.. _mod-properties:
+
+Exposing C module properties
+============================
+
+A module can offer NULL-terminated list of *properties*, each property is essentially a callable with free-form JSON input/output.
+JSON was chosen as an interchangeable format that doesn't require any schema beforehand, so you can do two things - query the module properties
+from external applications or between modules (e.g. `statistics` module can query `cache` module for memory usage).
+JSON was chosen not because it's the most efficient protocol, but because it's easy to read and write and interface to outside world.
+
+.. note:: The ``void *env`` is a generic module interface. Since we're implementing daemon modules, the pointer can be cast to ``struct engine*``.
+ This is guaranteed by the implemented API version (see `Writing a module in C`_).
+
+Here's an example how a module can expose its property:
+
+.. code-block:: c
+
+ char* get_size(void *env, struct kr_module *m,
+ const char *args)
+ {
+ /* Get cache from engine. */
+ struct engine *engine = env;
+ struct kr_cache *cache = &engine->resolver.cache;
+ /* Read item count */
+ int count = (cache->api)->count(cache->db);
+ char *result = NULL;
+ asprintf(&result, "{ \"result\": %d }", count);
+
+ return result;
+ }
+
+ struct kr_prop *cache_props(void)
+ {
+ static struct kr_prop prop_list[] = {
+ /* Callback, Name, Description */
+ {&get_size, "get_size", "Return number of records."},
+ {NULL, NULL, NULL}
+ };
+ return prop_list;
+ }
+
+ KR_MODULE_EXPORT(cache)
+
+Once you load the module, you can call the module property from the interactive console.
+*Note:* the JSON output will be transparently converted to Lua tables.
+
+.. code-block:: bash
+
+ $ kresd
+ ...
+ [system] started in interactive mode, type 'help()'
+ > modules.load('cached')
+ > cached.get_size()
+ [size] => 53
+
+.. No idea what this talks about, but kept for now:
+.. *Note:* this relies on function pointers, so the same ``static inline`` trick as for the ``Layer()`` is required for C.
+
+Special properties
+------------------
+
+If the module declares properties ``get`` or ``set``, they can be used in the Lua interpreter as
+regular tables.
+
diff --git a/modules/bogus_log/.packaging/test.config b/modules/bogus_log/.packaging/test.config
new file mode 100644
index 0000000..bf1c821
--- /dev/null
+++ b/modules/bogus_log/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('bogus_log')
+assert(bogus_log)
+quit()
diff --git a/modules/bogus_log/README.rst b/modules/bogus_log/README.rst
new file mode 100644
index 0000000..d60c278
--- /dev/null
+++ b/modules/bogus_log/README.rst
@@ -0,0 +1,45 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-bogus_log:
+
+DNSSEC validation failure logging
+=================================
+
+This module logs a message for each DNSSEC validation failure (on ``notice`` :func:`level <log_level>`).
+It is meant to provide hint to operators which queries should be
+investigated using diagnostic tools like DNSViz_.
+
+Add following line to your configuration file to enable it:
+
+.. code-block:: lua
+
+ modules.load('bogus_log')
+
+Example of error message logged by this module:
+
+.. code-block:: none
+
+ [dnssec] validation failure: dnssec-failed.org. DNSKEY
+
+.. _DNSViz: http://dnsviz.net/
+
+List of most frequent queries which fail as DNSSEC bogus can be obtained at run-time:
+
+.. code-block:: lua
+
+ > bogus_log.frequent()
+ {
+ {
+ ['count'] = 1,
+ ['name'] = 'dnssec-failed.org.',
+ ['type'] = 'DNSKEY',
+ },
+ {
+ ['count'] = 13,
+ ['name'] = 'rhybar.cz.',
+ ['type'] = 'DNSKEY',
+ },
+ }
+
+Please note that in future this module might be replaced
+with some other way to log this information.
diff --git a/modules/bogus_log/bogus_log.c b/modules/bogus_log/bogus_log.c
new file mode 100644
index 0000000..7b36187
--- /dev/null
+++ b/modules/bogus_log/bogus_log.c
@@ -0,0 +1,135 @@
+/* Copyright (C) Knot Resolver contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * This module logs (query name, type) pairs which failed DNSSEC validation. */
+
+#include <libknot/packet/pkt.h>
+#include <libknot/dname.h>
+#include <ccan/json/json.h>
+#include <contrib/cleanup.h>
+
+#include "daemon/engine.h"
+#include "lib/layer.h"
+#include "lib/generic/lru.h"
+
+#ifdef LRU_REP_SIZE
+ #define FREQUENT_COUNT LRU_REP_SIZE /* Size of frequent tables */
+#else
+ #define FREQUENT_COUNT 5000 /* Size of frequent tables */
+#endif
+
+/** @internal LRU hash of most frequent names. */
+typedef lru_t(unsigned) namehash_t;
+
+/** @internal Stats data structure. */
+struct stat_data {
+ namehash_t *frequent;
+};
+
+static int consume(kr_layer_t *ctx, knot_pkt_t *pkt)
+{
+ if (!(ctx->state & KR_STATE_FAIL)
+ || !ctx->req
+ || !ctx->req->current_query
+ || !ctx->req->current_query->flags.DNSSEC_BOGUS
+ || knot_wire_get_qdcount(pkt->wire) != 1)
+ return ctx->state;
+
+ auto_free char *qname_text = kr_dname_text(knot_pkt_qname(pkt));
+ auto_free char *qtype_text = kr_rrtype_text(knot_pkt_qtype(pkt));
+
+ kr_log_notice(DNSSEC, "validation failure: %s %s\n", qname_text, qtype_text);
+
+ /* log of most frequent bogus queries */
+ uint16_t type = knot_pkt_qtype(pkt);
+ char key[sizeof(type) + KNOT_DNAME_MAXLEN];
+ memcpy(key, &type, sizeof(type));
+ int key_len = knot_dname_to_wire((uint8_t *)key + sizeof(type), knot_pkt_qname(pkt), KNOT_DNAME_MAXLEN);
+ if (key_len >= 0) {
+ struct kr_module *module = ctx->api->data;
+ struct stat_data *data = module->data;
+ unsigned *count = lru_get_new(data->frequent, key, key_len+sizeof(type), NULL);
+ if (count)
+ *count += 1;
+ }
+
+ return ctx->state;
+}
+
+/** @internal Helper for dump_list: add a single namehash_t item to JSON. */
+static enum lru_apply_do dump_value(const char *key, uint len, unsigned *val, void *baton)
+{
+ uint16_t key_type = 0;
+ /* Extract query name, type and counter */
+ memcpy(&key_type, key, sizeof(key_type));
+ KR_DNAME_GET_STR(key_name, (uint8_t *)key + sizeof(key_type));
+ KR_RRTYPE_GET_STR(type_str, key_type);
+
+ /* Convert to JSON object */
+ JsonNode *json_val = json_mkobject();
+ json_append_member(json_val, "count", json_mknumber(*val));
+ json_append_member(json_val, "name", json_mkstring(key_name));
+ json_append_member(json_val, "type", json_mkstring(type_str));
+ json_append_element((JsonNode *)baton, json_val);
+ return LRU_APPLY_DO_NOTHING; // keep the item
+}
+
+/**
+ * List frequent names.
+ *
+ * Output: [{ count: <counter>, name: <qname>, type: <qtype>}, ... ]
+ */
+static char* dump_list(void *env, struct kr_module *module, const char *args, namehash_t *table)
+{
+ if (!table) {
+ return NULL;
+ }
+ JsonNode *root = json_mkarray();
+ lru_apply(table, dump_value, root);
+ char *ret = json_encode(root);
+ json_delete(root);
+ return ret;
+}
+
+static char* dump_frequent(void *env, struct kr_module *module, const char *args)
+{
+ struct stat_data *data = module->data;
+ return dump_list(env, module, args, data->frequent);
+}
+
+KR_EXPORT
+int bogus_log_init(struct kr_module *module)
+{
+ static kr_layer_api_t layer = {
+ .consume = &consume,
+ };
+ layer.data = module;
+ module->layer = &layer;
+
+ static const struct kr_prop props[] = {
+ { &dump_frequent, "frequent", "List most frequent queries.", },
+ { NULL, NULL, NULL }
+ };
+ module->props = props;
+
+ struct stat_data *data = calloc(1, sizeof(*data));
+ if (!data) {
+ return kr_error(ENOMEM);
+ }
+ module->data = data;
+ lru_create(&data->frequent, FREQUENT_COUNT, NULL, NULL);
+ return kr_ok();
+}
+
+KR_EXPORT
+int bogus_log_deinit(struct kr_module *module)
+{
+ struct stat_data *data = module->data;
+ if (data) {
+ lru_free(data->frequent);
+ free(data);
+ }
+ return kr_ok();
+}
+
+KR_MODULE_EXPORT(bogus_log)
diff --git a/modules/bogus_log/meson.build b/modules/bogus_log/meson.build
new file mode 100644
index 0000000..2dcf87f
--- /dev/null
+++ b/modules/bogus_log/meson.build
@@ -0,0 +1,21 @@
+# C module: bogus_log
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+bogus_log_src = files([
+ 'bogus_log.c',
+])
+c_src_lint += bogus_log_src
+
+bogus_log_mod = shared_module(
+ 'bogus_log',
+ bogus_log_src,
+ dependencies: libknot,
+ include_directories: mod_inc_dir,
+ name_prefix: '',
+ install: true,
+ install_dir: modules_dir,
+)
+
+integr_tests += [
+ ['bogus_log', meson.current_source_dir() / 'test.integr'],
+]
diff --git a/modules/bogus_log/test.integr/deckard.yaml b/modules/bogus_log/test.integr/deckard.yaml
new file mode 100644
index 0000000..ecd6cc5
--- /dev/null
+++ b/modules/bogus_log/test.integr/deckard.yaml
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+programs:
+- name: kresd
+ binary: kresd
+ additional:
+ - --noninteractive
+ templates:
+ - modules/bogus_log/test.integr/kresd_config.j2
+ - tests/integration/hints_zone.j2
+ configs:
+ - config
+ - hints
+noclean: True
diff --git a/modules/bogus_log/test.integr/kresd_config.j2 b/modules/bogus_log/test.integr/kresd_config.j2
new file mode 100644
index 0000000..0471a27
--- /dev/null
+++ b/modules/bogus_log/test.integr/kresd_config.j2
@@ -0,0 +1,90 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local ffi = require('ffi')
+
+{% for TAF in TRUST_ANCHOR_FILES %}
+trust_anchors.add_file('{{TAF}}')
+{% endfor %}
+
+{% raw %}
+modules.load('bogus_log')
+
+function check_stats(got)
+ log_info(ffi.C.LOG_GRP_TESTS, 'checking if bogus_log.frequent values match expected values:')
+ local expected = {
+ [1] = {
+ ['type'] = 'DNSKEY',
+ ['count'] = 2,
+ ['name'] = '.',
+ }
+ }
+ print(table_print(expected))
+
+ if table_print(expected) == table_print(got) then
+ log_info(ffi.C.LOG_GRP_TESTS, 'no problem found')
+ return policy.DENY_MSG('Ok')
+ else
+ log_info(ffi.C.LOG_GRP_TESTS, 'mismatch!')
+ return policy.DENY_MSG('bogus_log.frequent mismatch, see logs')
+ end
+end
+
+function reply_result(state, req)
+ local got = bogus_log.frequent()
+ print('current bogus_log.frequent() values:')
+ print(table_print(got))
+ local result = check_stats(got)
+ return result(state, req)
+end
+policy.add(policy.pattern(reply_result, 'bogus_log.test.'))
+
+-- 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
+
+_hint_root_file('hints')
+cache.size = 2*MB
+log_level('debug')
+{% endraw %}
+
+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(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 --git a/modules/bogus_log/test.integr/val_minimal_expiredsignature.rpl b/modules/bogus_log/test.integr/val_minimal_expiredsignature.rpl
new file mode 100644
index 0000000..46c228c
--- /dev/null
+++ b/modules/bogus_log/test.integr/val_minimal_expiredsignature.rpl
@@ -0,0 +1,125 @@
+; SPDX-License-Identifier: GPL-3.0-or-later
+ trust-anchor: ". IN DS 49060 8 2 E7B1EB56D7D5791B3D45630FEAA9C823DB84B385ACEEAC5F44DD08885C36700F"
+ val-override-date: "20170410000000"
+ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
+ query-minimization: off
+CONFIG_END
+
+SCENARIO_BEGIN Date after expiration of signatures.
+
+; K.ROOT-SERVERS.NET.
+RANGE_BEGIN 0 100
+ ADDRESS 193.0.14.129
+ ADDRESS 2001:7fd::1
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS k.root-servers.net.
+. 518400 IN RRSIG NS 8 0 518400 20170409093827 20170310093827 20661 . uBuJpbRh1NYVciSKK0r3SA6NFnqE4s/+CqLfTXu26/HrY5c1aOhQHXZM cCDDjfPGFa7Eh4mqF0i9I+i+bFbYQitI1Heexye599VE19REbVsK4qaU xkArvt9k6HVqd/7BXXUyzLN1N0CScdyuT5tiEI9154SDNVpnC+z8i2u0 9hW8JEk4qqVWX/I1MYQB/UOcFSeDhD1Qku/26opqDuLl/1eaShxhMQ/c rjzOb5ZYzD0x+TUJZMYSOMwAraaFuYTT84oe6QYY+EGctAk1b50nA/5E C3Tm/xGuo9ioVtYhTwoo1XDUVeHmghdILjQZvR4pOSZoRGGP9ovb08Qg OmPXuQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. IN DNSKEY 256 3 8 AwEAAe1oA46eOLNris1CtS0qM5TdMESK6i4hpalqa6JDv57eOUkaOeje ZW1tIFUokmaK7kuKEFEosddA89CYM8rt2RbC+sfKalbHAWOus0tXZyAL efb2sW95QRzyG6LNul0jQFn9eYWBUHrVe5Wqd0zrFCbTQLUhELSfrlkI UBpO/xKaGinRHX2JjyOnle4aPZY3bEVa/+KyY2ZU6UC4SBo3aHXanP26 ok91rOTmpTWp64ybsMdCXOU8deyuQFQf6q8DhIDmJrkymhX1MXWQQlE0 fAYIYf8/t9OCwucg8oEg4FPU8Gb4Zm/l6PgO4HFkFjBT6iGFCQt3qXe2 Qe3alUWoATc=
+. IN DNSKEY 257 3 8 AwEAAb8sZgVVa02muJ+/+SVhJAvz2EWKGEGquhPbQXuF6ALBYoF4KWTO bZVF8sIVTGoaX5+UWkwwHthg7RwS1DALT/AJymYeHhUwA04gLsfCZ/cv BjmRy5RozeSJ1uxAhoCYHCT2hQBZ0cH0n8roXFXI2Y+6708pO1IBkTPT 9MpAGfezTtGYOortbSn+vqT/Zu8jOpNwkleXON4rlZRBZPd4JUMGL9Y5 N/j6+ClYeM+eFQTKXrLi1oC+0yK1sG5OlqrBDhAhBnz+IhfZz4TOkqJ9 Li2BVMatHBeB9GQHtu0FZuC3J0EQgiZxvq1RgkefFJAiB+5uVRN8U7up 5mLDxSgmT0M=
+. 2592000 IN RRSIG DNSKEY 8 0 3600000 20170409093827 20170310093827 49060 . G7s3QiWNgOsl+LoG6OKjdBHPcFyhmCS17GFnaKjfJNdPQaFL5nM/vrXo eUIIdJXAvjj62TY7wTyFlnx3yjK93RVGKEEySpGC/1gkn5AdjVoQszog IxYjKzubizULSaX7SQ3/Ar+uHLxakdS1qgNdFu6hHCl857LJPtmC8SJt iFUmm5HFyARokMrfA88VrFRKEqojcCWajeZMfRtgBipFJZoYgPUCaFlz 8OupNdNUWCbGhnDWrXCWMzeKVXTQVlJf75PXXgtkuBUmr5RSWu7AYr+c wTJ4E4610goRqYxnZ33efKE/MuhKeY66xelPh0sirPrBMR5JAlyjV3k1 qDzhcQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+k.root-servers.net. IN AAAA
+SECTION ANSWER
+k.root-servers.net. 3600000 IN AAAA 2001:7fd::1
+k.root-servers.net. 2592000 IN RRSIG AAAA 8 3 3600000 20170409093827 20170310093827 20661 . qjVXwuxzmoRhdrXyQvKfrrzFxGiYuTTJHxwZPasJ1nVmN48dPyU6wA55 JeqoJv1Jm+XvIL1q0WtX6Zh6KLt6vVjHuMkhmFuIZYkFi/dmsEwFY8C0 ebyXyztQT5+6FOSVTAKacYc40LfBo8FqEn8RYlCu1mkAd8ANvvLrdLWW W03LVOY7JlCzyrKlAlmPmuV8z+e9PxNkUh6KfTEvAReoAAX7wYZkdefg 2d64c7rNWXvYm6LxBX6qeQ39d5WyKc8v+G01DJuDzs2Tx368QoK86vm/ qo9ERdT7koRt+gBZNYv8V4fh2SjaFsy2TJq/tiYcSia9snGDTFj6LWVM 6sBCYQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+k.root-servers.net. IN A
+SECTION ANSWER
+k.root-servers.net. 3600000 IN A 193.0.14.129
+k.root-servers.net. 2592000 IN RRSIG A 8 3 3600000 20170409093827 20170310093827 20661 . feIXXjskcsyH+ALZu67GVDaPWXjUGTWsTlDwzgJcLBzSuRVY/GVD5Z1Q B4/oUW99rLKB5bNS1MuasZ+nZFV67sBwJk1+SqNB2bAe7G5Tv1sR2Qgi qDAoB37YDVk5JGHfuxByLYbAVG9PrPXT60BN17OYrD/TFPzprye65gk3 7l9kPpAlblcsqdvh5piKrWc7VBcyMhlp56qdASNAl+Lrb+i0DZYyJXh+ b8LV5g5zp9FaVGKe0Gi4+yDXVjcM6VEtuNRAu2+flLoc3ho6qQF1Po4Y wueL72I+yFoUxkIOJvK47eWb+YUBIBK/L8/ORjYoLBRsrbc79wb0I3Zj Xy6O4Q==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype
+ADJUST copy_id copy_query
+REPLY QR AA REFUSED
+SECTION QUESTION
+. IN RRSIG
+SECTION ANSWER
+ENTRY_END
+RANGE_END
+
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD DO
+SECTION QUESTION
+. IN NS
+ENTRY_END
+
+
+STEP 10 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA DO SERVFAIL
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+ENTRY_END
+
+STEP 11 QUERY
+ENTRY_BEGIN
+REPLY RD DO
+SECTION QUESTION
+. IN NS
+ENTRY_END
+
+STEP 12 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA DO SERVFAIL
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+ENTRY_END
+
+STEP 20 QUERY
+ENTRY_BEGIN
+REPLY RD DO
+SECTION QUESTION
+bogus_log.test. IN TXT
+ENTRY_END
+
+STEP 21 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR AA RD RA DO NXDOMAIN
+SECTION QUESTION
+bogus_log.test. IN TXT
+SECTION AUTHORITY
+bogus_log.test. 10800 IN SOA bogus_log.test. nobody.invalid. 1 3600 1200 604800 10800
+SECTION ADDITIONAL
+explanation.invalid. 10800 IN TXT "Ok"
+ENTRY_END
+
+SCENARIO_END
diff --git a/modules/cookies/README.rst b/modules/cookies/README.rst
new file mode 100644
index 0000000..b8aba8a
--- /dev/null
+++ b/modules/cookies/README.rst
@@ -0,0 +1,56 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-cookies:
+
+DNS Cookies
+===========
+
+The module performs most of the :rfc:`7873` DNS cookies functionality. Its main purpose is to check the cookies of inbound queries and responses. It is also used to alter the behaviour of the cookie functionality.
+
+Example Configuration
+---------------------
+
+.. code-block:: lua
+
+ -- Load the module before the 'iterate' layer.
+ modules = {
+ 'cookies < iterate'
+ }
+
+ -- Configure the client part of the resolver. Set 8 bytes of the client
+ -- secret and choose the hashing algorithm to be used.
+ -- Use a string composed of hexadecimal digits to set the secret.
+ cookies.config { client_secret = '0123456789ABCDEF',
+ client_cookie_alg = 'FNV-64' }
+
+ -- Configure the server part of the resolver.
+ cookies.config { server_secret = 'FEDCBA9876543210',
+ server_cookie_alg = 'FNV-64' }
+
+ -- Enable client cookie functionality. (Add cookies into outbound
+ -- queries.)
+ cookies.config { client_enabled = true }
+
+ -- Enable server cookie functionality. (Handle cookies in inbound
+ -- requests.)
+ cookies.config { server_enabled = true }
+
+.. tip:: If you want to change several parameters regarding the client or server configuration then do it within a single ``cookies.config()`` invocation.
+
+.. warning:: The module must be loaded before any other module that has direct influence on query processing and response generation. The module must be able to intercept an incoming query before the processing of the actual query starts. It must also be able to check the cookies of inbound responses and eventually discard them before they are handled by other functional units.
+
+Properties
+----------
+
+.. function:: cookies.config(configuration)
+
+ :param table configuration: part of cookie configuration to be changed, may be called without parameter
+ :return: JSON dictionary containing current configuration
+
+ The function may be called without any parameter. In such case it only returns current configuration. The returned JSON also contains available algorithm choices.
+
+Dependencies
+------------
+
+* `Nettle <https://www.lysator.liu.se/~nisse/nettle/>`_ required for HMAC-SHA256
+
diff --git a/modules/cookies/cookiectl.c b/modules/cookies/cookiectl.c
new file mode 100644
index 0000000..f1ab80a
--- /dev/null
+++ b/modules/cookies/cookiectl.c
@@ -0,0 +1,689 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <ccan/json/json.h>
+#include <ctype.h>
+#include <libknot/rrtype/opt-cookie.h>
+#include <libknot/db/db_lmdb.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/cookies/alg_containers.h"
+#include "modules/cookies/cookiectl.h"
+
+#define NAME_CLIENT_ENABLED "client_enabled"
+#define NAME_CLIENT_SECRET "client_secret"
+#define NAME_CLIENT_COOKIE_ALG "client_cookie_alg"
+#define NAME_AVAILABLE_CLIENT_COOKIE_ALGS "available_client_cookie_algs"
+
+#define NAME_SERVER_ENABLED "server_enabled"
+#define NAME_SERVER_SECRET "server_secret"
+#define NAME_SERVER_COOKIE_ALG "server_cookie_alg"
+#define NAME_AVAILABLE_SERVER_COOKIE_ALGS "available_server_cookie_algs"
+
+/**
+ * @brief Initialises cookie control context.
+ * @param ctx cookie control context
+ */
+static void kr_cookie_ctx_init(struct kr_cookie_ctx *ctx)
+{
+ if (!ctx) {
+ return;
+ }
+
+ memset(ctx, 0, sizeof(*ctx));
+
+ ctx->clnt.current.alg_id = ctx->clnt.recent.alg_id = -1;
+ ctx->srvr.current.alg_id = ctx->srvr.recent.alg_id = -1;
+}
+
+/**
+ * @brief Check whether node holds proper 'enabled' value.
+ * @param node JSON node holding the value
+ * @return true if value OK
+ */
+static bool enabled_ok(const JsonNode *node)
+{
+ if (kr_fails_assert(node))
+ return false;
+
+ return node->tag == JSON_BOOL;
+}
+
+/**
+ * @brief Check whether node holds proper 'secret' value.
+ * @param node JSON node holding the value
+ * @return true if value OK
+ */
+static bool secret_ok(const JsonNode *node)
+{
+ if (kr_fails_assert(node))
+ return false;
+
+ if (node->tag != JSON_STRING) {
+ return false;
+ }
+
+ const char *hexstr = node->string_;
+
+ size_t len = strlen(hexstr);
+ if ((len % 2) != 0) {
+ return false;
+ }
+ /* A check for minimal required length could also be performed. */
+
+ for (size_t i = 0; i < len; ++i) {
+ if (!isxdigit(tolower(hexstr[i]))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * @brief Find hash function with given name.
+ * @param node JSON node holding the value
+ * @param table lookup table with algorithm names
+ * @return pointer to table entry or NULL on error if does not exist
+ */
+static const knot_lookup_t *hash_func_lookup(const JsonNode *node,
+ const knot_lookup_t table[])
+{
+ if (!node || node->tag != JSON_STRING) {
+ return NULL;
+ }
+
+ return knot_lookup_by_name(table, node->string_);
+}
+
+/**
+ * @brief Creates a cookie secret structure.
+ * @param size size of the actual secret
+ * @param zero set to true if value should be cleared
+ * @return pointer to new structure, NULL on failure or if @size is zero
+ */
+static struct kr_cookie_secret *new_cookie_secret(size_t size, bool zero)
+{
+ if (size == 0) {
+ return NULL;
+ }
+
+ struct kr_cookie_secret *sq = malloc(sizeof(*sq) + size);
+ if (!sq) {
+ return NULL;
+ }
+
+ sq->size = size;
+ if (zero) {
+ memset(sq->data, 0, size);
+ }
+ return sq;
+}
+
+/**
+ * @brief Clone a cookie secret.
+ * @param sec secret to be cloned
+ * @return pointer to new structure, NULL on failure or if @size is zero
+ */
+static struct kr_cookie_secret *clone_cookie_secret(const struct kr_cookie_secret *sec)
+{
+ if (!sec || sec->size == 0) {
+ return NULL;
+ }
+
+ struct kr_cookie_secret *sq = malloc(sizeof(*sq) + sec->size);
+ if (!sq) {
+ return NULL;
+ }
+
+ sq->size = sec->size;
+ memcpy(sq->data, sec->data, sq->size);
+ return sq;
+}
+
+static int hexchar2val(int d)
+{
+ if (('0' <= d) && (d <= '9')) {
+ return d - '0';
+ } else if (('a' <= d) && (d <= 'f')) {
+ return d - 'a' + 0x0a;
+ } else {
+ return -1;
+ }
+}
+
+static int hexval2char(int i)
+{
+ if ((0 <= i) && (i <= 9)) {
+ return i + '0';
+ } else if ((0x0a <= i) && (i <= 0x0f)) {
+ return i - 0x0a + 'A';
+ } else {
+ return -1;
+ }
+}
+
+/**
+ * @brief Converts string containing two-digit hexadecimal number into int.
+ * @param hexstr hexadecimal string
+ * @return -1 on error, value from 0 to 255 else.
+ */
+static int hexbyte2int(const char *hexstr)
+{
+ if (!hexstr) {
+ return -1;
+ }
+
+ int dhi = tolower(hexstr[0]);
+ if (!isxdigit(dhi)) {
+ /* Exit also on empty string. */
+ return -1;
+ }
+ int dlo = tolower(hexstr[1]);
+ if (!isxdigit(dlo)) {
+ return -1;
+ }
+
+ dhi = hexchar2val(dhi);
+ if (kr_fails_assert(dhi != -1))
+ return -1;
+ dlo = hexchar2val(dlo);
+ if (kr_fails_assert(dlo != -1))
+ return -1;
+
+ return (dhi << 4) | dlo;
+}
+
+/**
+ * @brief Writes two hexadecimal digits (two byes) into given memory location.
+ * @param tgt target location
+ * @param i number from 0 to 255
+ * @return 0 on success, -1 on failure
+ */
+static int int2hexbyte(char *tgt, int i)
+{
+ if (!tgt || i < 0x00 || i > 0xff) {
+ return -1;
+ }
+
+ int ilo = hexval2char(i & 0x0f);
+ if (kr_fails_assert(ilo != -1))
+ return -1;
+ int ihi = hexval2char((i >> 4) & 0x0f);
+ if (kr_fails_assert(ihi != -1))
+ return -1;
+
+ tgt[0] = ihi;
+ tgt[1] = ilo;
+
+ return 0;
+}
+
+/**
+ * @brief Reads a string containing hexadecimal values.
+ * @note String must consist of hexadecimal digits only and must have even
+ * non-zero length.
+ */
+static struct kr_cookie_secret *new_sq_from_hexstr(const char *hexstr)
+{
+ if (!hexstr) {
+ return NULL;
+ }
+
+ size_t len = strlen(hexstr);
+ if ((len % 2) != 0) {
+ return NULL;
+ }
+
+ struct kr_cookie_secret *sq = new_cookie_secret(len / 2, false);
+ if (!sq) {
+ return NULL;
+ }
+
+ uint8_t *data = sq->data;
+ for (size_t i = 0; i < len; i += 2) {
+ int num = hexbyte2int(hexstr + i);
+ if (num == -1) {
+ free(sq);
+ return NULL;
+ }
+ if (kr_fails_assert(0x00 <= num && num <= 0xff)) {
+ free(sq);
+ return NULL;
+ }
+ *data = num;
+ ++data;
+ }
+
+ return sq;
+}
+
+/**
+ * @brief Creates new secret.
+ * @param node JSON node holding the secret value
+ * @return pointer to newly allocated secret, NULL on error
+ */
+static struct kr_cookie_secret *create_secret(const JsonNode *node)
+{
+ if (!node) {
+ return NULL;
+ }
+
+ if (node->tag != JSON_STRING) {
+ return NULL;
+ }
+
+ return new_sq_from_hexstr(node->string_);
+}
+
+/**
+ * @brief Check whether configuration node contains valid values.
+ */
+static bool configuration_node_ok(const JsonNode *node)
+{
+ if (kr_fails_assert(node))
+ return false;
+
+ if (!node->key) {
+ /* All top most nodes must have names. */
+ return false;
+ }
+
+ if (strcmp(node->key, NAME_CLIENT_ENABLED) == 0) {
+ return enabled_ok(node);
+ } else if (strcmp(node->key, NAME_CLIENT_SECRET) == 0) {
+ return secret_ok(node);
+ } else if (strcmp(node->key, NAME_CLIENT_COOKIE_ALG) == 0) {
+ return hash_func_lookup(node, kr_cc_alg_names) != NULL;
+ } else if (strcmp(node->key, NAME_SERVER_ENABLED) == 0) {
+ return enabled_ok(node);
+ } else if (strcmp(node->key, NAME_SERVER_SECRET) == 0) {
+ return secret_ok(node);
+ } else if (strcmp(node->key, NAME_SERVER_COOKIE_ALG) == 0) {
+ return hash_func_lookup(node, kr_sc_alg_names) != NULL;
+ }
+
+ return false;
+}
+
+/**
+ * @brief Creates a new string from secret quantity.
+ * @param sq secret quantity
+ * @return newly allocated string or NULL on error
+ */
+static char *new_hexstr_from_sq(const struct kr_cookie_secret *sq)
+{
+ if (!sq) {
+ return NULL;
+ }
+
+ char *new_str = malloc((sq->size * 2) + 1);
+ if (!new_str) {
+ return NULL;
+ }
+
+ char *tgt = new_str;
+ for (size_t i = 0; i < sq->size; ++i) {
+ if (0 != int2hexbyte(tgt, sq->data[i])) {
+ free(new_str);
+ return NULL;
+ }
+ tgt += 2;
+ }
+
+ *tgt = '\0';
+ return new_str;
+}
+
+static bool read_secret(JsonNode *root, const char *node_name,
+ const struct kr_cookie_secret *secret)
+{
+ if (kr_fails_assert(root && node_name && secret))
+ return false;
+
+ char *secret_str = new_hexstr_from_sq(secret);
+ if (!secret_str) {
+ return false;
+ }
+
+ JsonNode *str_node = json_mkstring(secret_str);
+ if (!str_node) {
+ free(secret_str);
+ return false;
+ }
+
+ json_append_member(root, node_name, str_node);
+
+ free(secret_str);
+ return true;
+}
+
+static bool read_available_hashes(JsonNode *root, const char *root_name,
+ const knot_lookup_t table[])
+{
+ if (kr_fails_assert(root && root_name && table))
+ return false;
+
+ JsonNode *array = json_mkarray();
+ if (!array) {
+ return false;
+ }
+
+ const knot_lookup_t *aux_ptr = table;
+ while (aux_ptr && (aux_ptr->id >= 0) && aux_ptr->name) {
+ JsonNode *element = json_mkstring(aux_ptr->name);
+ if (!element) {
+ goto fail;
+ }
+ json_append_element(array, element);
+ ++aux_ptr;
+ }
+
+ json_append_member(root, root_name, array);
+
+ return true;
+
+fail:
+ if (array) {
+ json_delete(array);
+ }
+ return false;
+}
+
+/**
+ * @brief Check whether new settings are different from the old ones.
+ */
+static bool is_modified(const struct kr_cookie_comp *running,
+ struct kr_cookie_secret *secr,
+ const knot_lookup_t *alg_lookup)
+{
+ if (kr_fails_assert(running))
+ return false;
+
+ if (alg_lookup && alg_lookup->id >= 0) {
+ if (running->alg_id != alg_lookup->id) {
+ return true;
+ }
+ }
+
+ if (secr) {
+ if (kr_fails_assert(secr->size > 0))
+ return false;
+ if (running->secr->size != secr->size ||
+ 0 != memcmp(running->secr->data, secr->data,
+ running->secr->size)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * @brief Returns newly allocated secret via pointer argument.
+ */
+static bool obtain_secret(JsonNode *root_node, struct kr_cookie_secret **secret,
+ const char *name)
+{
+ if (kr_fails_assert(secret && name))
+ return false;
+
+ const JsonNode *node;
+ if ((node = json_find_member(root_node, name)) != NULL) {
+ *secret = create_secret(node);
+ if (!*secret) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * @brief Updates the current configuration and moves current to recent.
+ */
+static void update_running(struct kr_cookie_settings *running,
+ struct kr_cookie_secret **secret,
+ const knot_lookup_t *alg_lookup)
+{
+ if (kr_fails_assert(running && secret) || kr_fails_assert(*secret || alg_lookup))
+ return;
+
+ running->recent.alg_id = -1;
+ free(running->recent.secr);
+ running->recent.secr = NULL;
+
+ running->recent.alg_id = running->current.alg_id;
+ if (alg_lookup) {
+ if (kr_fails_assert(alg_lookup->id >= 0))
+ return;
+ running->current.alg_id = alg_lookup->id;
+ }
+
+ if (*secret) {
+ running->recent.secr = running->current.secr;
+ running->current.secr = *secret;
+ *secret = NULL;
+ } else {
+ running->recent.secr = clone_cookie_secret(running->current.secr);
+ }
+}
+
+/**
+ * @brief Applies modification onto client/server running configuration.
+ * @note The @a secret is going to be consumed.
+ * @param secret pointer to new secret
+ * @param alg_lookup new algorithm
+ * @param enabled JSON node holding boolean value
+ */
+static void apply_changes(struct kr_cookie_settings *running,
+ struct kr_cookie_secret **secret,
+ const knot_lookup_t *alg_lookup,
+ const JsonNode *enabled)
+{
+ if (kr_fails_assert(running && secret))
+ return;
+
+ if (is_modified(&running->current, *secret, alg_lookup)) {
+ update_running(running, secret, alg_lookup);
+ }
+
+ if (enabled) {
+ kr_assert(enabled->tag == JSON_BOOL);
+ running->enabled = enabled->bool_;
+ }
+}
+
+/**
+ * @brief Applies configuration.
+ *
+ * @note The function must be called after the input values have been checked
+ * for validity. Only first found values are applied.
+ *
+ * @param ctx cookie configuration context
+ * @param root_node JSON root node
+ * @return true if changes were applied
+ */
+static bool config_apply_json(struct kr_cookie_ctx *ctx, JsonNode *root_node)
+{
+ if (kr_fails_assert(ctx && root_node))
+ return;
+
+ /*
+ * These must be allocated before actual change. Allocation failure
+ * should not leave configuration in inconsistent state.
+ */
+ struct kr_cookie_secret *new_clnt_secret = NULL;
+ struct kr_cookie_secret *new_srvr_secret = NULL;
+ if (!obtain_secret(root_node, &new_clnt_secret, NAME_CLIENT_SECRET)) {
+ return false;
+ }
+ if (!obtain_secret(root_node, &new_srvr_secret, NAME_SERVER_SECRET)) {
+ free(new_clnt_secret);
+ return false;
+ }
+
+ /* Algorithm pointers. */
+ const knot_lookup_t *clnt_lookup = hash_func_lookup(json_find_member(root_node, NAME_CLIENT_COOKIE_ALG), kr_cc_alg_names);
+ const knot_lookup_t *srvr_lookup = hash_func_lookup(json_find_member(root_node, NAME_SERVER_COOKIE_ALG), kr_sc_alg_names);
+
+ const JsonNode *clnt_enabled_node = json_find_member(root_node, NAME_CLIENT_ENABLED);
+ const JsonNode *srvr_enabled_node = json_find_member(root_node, NAME_SERVER_ENABLED);
+
+ apply_changes(&ctx->clnt, &new_clnt_secret, clnt_lookup, clnt_enabled_node);
+ apply_changes(&ctx->srvr, &new_srvr_secret, srvr_lookup, srvr_enabled_node);
+
+ /*
+ * Allocated secrets should be already consumed. There is no need to
+ * free them.
+ */
+
+ return true;
+}
+
+bool config_apply(struct kr_cookie_ctx *ctx, const char *args)
+{
+ if (!ctx) {
+ return false;
+ }
+
+ if (!args || !strlen(args)) {
+ return true;
+ }
+
+ if (!args || !strlen(args)) {
+ return true;
+ }
+
+ bool success = false;
+
+ /* Check whether all supplied data are valid. */
+ JsonNode *root_node = json_decode(args);
+ if (!root_node) {
+ return false;
+ }
+ JsonNode *node;
+ json_foreach (node, root_node) {
+ success = configuration_node_ok(node);
+ if (!success) {
+ break;
+ }
+ }
+
+ /* Apply configuration if values seem to be OK. */
+ if (success) {
+ success = config_apply_json(ctx, root_node);
+ }
+
+ json_delete(root_node);
+
+ return success;
+}
+
+char *config_read(struct kr_cookie_ctx *ctx)
+{
+ if (!ctx) {
+ return NULL;
+ }
+
+ const knot_lookup_t *lookup;
+ char *result;
+ JsonNode *root_node = json_mkobject();
+ if (!root_node) {
+ return NULL;
+ }
+
+ json_append_member(root_node, NAME_CLIENT_ENABLED,
+ json_mkbool(ctx->clnt.enabled));
+
+ read_secret(root_node, NAME_CLIENT_SECRET, ctx->clnt.current.secr);
+
+ lookup = knot_lookup_by_id(kr_cc_alg_names, ctx->clnt.current.alg_id);
+ if (lookup) {
+ json_append_member(root_node, NAME_CLIENT_COOKIE_ALG,
+ json_mkstring(lookup->name));
+ }
+
+ read_available_hashes(root_node, NAME_AVAILABLE_CLIENT_COOKIE_ALGS,
+ kr_cc_alg_names);
+
+ json_append_member(root_node, NAME_SERVER_ENABLED,
+ json_mkbool(ctx->srvr.enabled));
+
+ read_secret(root_node, NAME_SERVER_SECRET, ctx->srvr.current.secr);
+
+ lookup = knot_lookup_by_id(kr_sc_alg_names, ctx->srvr.current.alg_id);
+ if (lookup) {
+ json_append_member(root_node, NAME_SERVER_COOKIE_ALG,
+ json_mkstring(lookup->name));
+ }
+
+ read_available_hashes(root_node, NAME_AVAILABLE_SERVER_COOKIE_ALGS,
+ kr_sc_alg_names);
+
+ result = json_encode(root_node);
+ json_delete(root_node);
+ return result;
+}
+
+int config_init(struct kr_cookie_ctx *ctx)
+{
+ if (!ctx) {
+ return kr_error(EINVAL);
+ }
+
+ kr_cookie_ctx_init(ctx);
+
+ struct kr_cookie_secret *cs = new_cookie_secret(KNOT_OPT_COOKIE_CLNT,
+ true);
+ struct kr_cookie_secret *ss = new_cookie_secret(KNOT_OPT_COOKIE_CLNT,
+ true);
+ if (!cs || !ss) {
+ free(cs);
+ free(ss);
+ return kr_error(ENOMEM);
+ }
+
+ const knot_lookup_t *clookup = knot_lookup_by_name(kr_cc_alg_names,
+ "FNV-64");
+ const knot_lookup_t *slookup = knot_lookup_by_name(kr_sc_alg_names,
+ "FNV-64");
+ if (!clookup || !slookup) {
+ free(cs);
+ free(ss);
+ return kr_error(ENOENT);
+ }
+
+ ctx->clnt.current.secr = cs;
+ ctx->clnt.current.alg_id = clookup->id;
+
+ ctx->srvr.current.secr = ss;
+ ctx->srvr.current.alg_id = slookup->id;
+
+ return kr_ok();
+}
+
+void config_deinit(struct kr_cookie_ctx *ctx)
+{
+ if (!ctx) {
+ return;
+ }
+
+ ctx->clnt.enabled = false;
+
+ free(ctx->clnt.recent.secr);
+ ctx->clnt.recent.secr = NULL;
+
+ free(ctx->clnt.current.secr);
+ ctx->clnt.current.secr = NULL;
+
+ ctx->srvr.enabled = false;
+
+ free(ctx->srvr.recent.secr);
+ ctx->srvr.recent.secr = NULL;
+
+ free(ctx->srvr.current.secr);
+ ctx->srvr.current.secr = NULL;
+}
diff --git a/modules/cookies/cookiectl.h b/modules/cookies/cookiectl.h
new file mode 100644
index 0000000..6740e16
--- /dev/null
+++ b/modules/cookies/cookiectl.h
@@ -0,0 +1,35 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "lib/cookies/control.h"
+
+/**
+ * @brief Sets cookie control context structure.
+ * @param ctx cookie control context
+ * @param args JSON string describing configuration changes
+ * @return true if changes successfully applied
+ */
+bool config_apply(struct kr_cookie_ctx *ctx, const char *args);
+
+/**
+ * @brief Reads cookie control context structure.
+ * @param ctx cookie control context
+ * @return JSON string or NULL on error
+ */
+char *config_read(struct kr_cookie_ctx *ctx);
+
+/**
+ * @brief Initialises cookie control context to default values.
+ * @param ctx cookie control context
+ * @return kr_ok() or error code
+ */
+int config_init(struct kr_cookie_ctx *ctx);
+
+/**
+ * @brief Clears the cookie control context.
+ * @param ctx cookie control context
+ */
+void config_deinit(struct kr_cookie_ctx *ctx);
diff --git a/modules/cookies/cookiemonster.c b/modules/cookies/cookiemonster.c
new file mode 100644
index 0000000..595317b
--- /dev/null
+++ b/modules/cookies/cookiemonster.c
@@ -0,0 +1,464 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <ccan/json/json.h>
+#include <libknot/db/db_lmdb.h>
+#include <libknot/error.h>
+#include <libknot/mm_ctx.h>
+#include <libknot/rrtype/opt-cookie.h>
+#include <libknot/version.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/cookies/alg_containers.h"
+#include "lib/cookies/control.h"
+#include "lib/cookies/helper.h"
+#include "lib/cookies/lru_cache.h"
+#include "lib/cookies/nonce.h"
+#include "lib/resolve.h"
+#include "lib/rplan.h"
+#include "modules/cookies/cookiemonster.h"
+
+#define VERBOSE_MSG(qry, ...) kr_log_q(qry, COOKIES, __VA_ARGS__)
+
+/**
+ * Obtain address from query/response context if if can be obtained.
+ * @param req resolution context
+ * @return pointer to where the server socket address, NULL if not provided within context
+ */
+static const struct sockaddr *passed_server_sockaddr(const struct kr_request *req)
+{
+ if (!req || !req->upstream.addr) {
+ return NULL;
+ }
+
+ if (req->upstream.addr->sa_family == AF_INET ||
+ req->upstream.addr->sa_family == AF_INET6) {
+ return req->upstream.addr;
+ }
+
+ return NULL;
+}
+
+/**
+ * Obtain pointer to server socket address that matches obtained cookie.
+ * @param srvr_sa server socket address
+ * @param cc client cookie from the response
+ * @param cc_len client cookie size
+ * @param clnt_sett client cookie settings structure
+ * @retval 1 if cookie matches current settings
+ * @retval 0 if cookie matches recent settings
+ * @return -1 if cookie does not match
+ * @return -2 on any error
+ */
+static int srvr_sockaddr_cc_check(const struct sockaddr *srvr_sa,
+ const uint8_t *cc, uint16_t cc_len,
+ const struct kr_cookie_settings *clnt_sett)
+{
+ if (kr_fails_assert(cc && cc_len > 0 && clnt_sett))
+ return -2;
+
+ if (!srvr_sa) {
+ return -2;
+ }
+
+ if (kr_fails_assert(clnt_sett->current.secr))
+ return -2;
+
+ /* The address must correspond with the client cookie. */
+ struct knot_cc_input input = {
+ .clnt_sockaddr = NULL, /* Not supported yet. */
+ .srvr_sockaddr = srvr_sa,
+ .secret_data = clnt_sett->current.secr->data,
+ .secret_len = clnt_sett->current.secr->size
+ };
+
+ const struct knot_cc_alg *cc_alg = kr_cc_alg_get(clnt_sett->current.alg_id);
+ if (!cc_alg) {
+ return -2;
+ }
+ int comp_ret = -1; /* Cookie does not match. */
+ int ret = knot_cc_check(cc, cc_len, &input, cc_alg);
+ if (ret == KNOT_EOK) {
+ comp_ret = 1;
+ } else {
+ cc_alg = kr_cc_alg_get(clnt_sett->recent.alg_id);
+ if (clnt_sett->recent.secr && cc_alg) {
+ input.secret_data = clnt_sett->recent.secr->data;
+ input.secret_len = clnt_sett->recent.secr->size;
+ ret = knot_cc_check(cc, cc_len, &input, cc_alg);
+ if (ret == KNOT_EOK) {
+ comp_ret = 0;
+ }
+ }
+ }
+
+ return comp_ret;
+}
+
+/**
+ * Obtain cookie from cache.
+ * @note Cookies with invalid length are ignored.
+ * @param cache cache context
+ * @param sa key value
+ * @param cookie_opt entire EDNS cookie option (including header)
+ * @return true if a cookie exists in cache
+ */
+static const uint8_t *get_cookie_opt(kr_cookie_lru_t *cache,
+ const struct sockaddr *sa)
+{
+ if (kr_fails_assert(cache && sa))
+ return NULL;
+
+ const uint8_t *cached_cookie_opt = kr_cookie_lru_get(cache, sa);
+ if (!cached_cookie_opt) {
+ return NULL;
+ }
+
+ size_t cookie_opt_size = KNOT_EDNS_OPTION_HDRLEN +
+ knot_edns_opt_get_length(cached_cookie_opt);
+ if (cookie_opt_size > KR_COOKIE_OPT_MAX_LEN) {
+ return NULL;
+ }
+
+ return cached_cookie_opt;
+}
+
+/**
+ * Check whether the supplied cookie is cached under the given key.
+ * @param cache cache context
+ * @param sa key value
+ * @param cookie_opt cookie option to search for
+ */
+static bool is_cookie_cached(kr_cookie_lru_t *cache, const struct sockaddr *sa,
+ const uint8_t *cookie_opt)
+{
+ if (kr_fails_assert(cache && sa && cookie_opt))
+ return false;
+
+ const uint8_t *cached_opt = get_cookie_opt(cache, sa);
+ if (!cached_opt) {
+ return false;
+ }
+
+ uint16_t cookie_opt_size = KNOT_EDNS_OPTION_HDRLEN +
+ knot_edns_opt_get_length(cookie_opt);
+ uint16_t cached_opt_size = KNOT_EDNS_OPTION_HDRLEN +
+ knot_edns_opt_get_length(cached_opt);
+
+ if (cookie_opt_size != cached_opt_size) {
+ return false;
+ }
+
+ return memcmp(cookie_opt, cached_opt, cookie_opt_size) == 0;
+}
+
+/**
+ * Check cookie content and store it to cache.
+ */
+static bool check_cookie_content_and_cache(const struct kr_cookie_settings *clnt_sett,
+ struct kr_request *req,
+ uint8_t *pkt_cookie_opt,
+ kr_cookie_lru_t *cache)
+{
+ if (kr_fails_assert(clnt_sett && req && pkt_cookie_opt && cache))
+ return false;
+
+ const uint8_t *pkt_cookie_data = knot_edns_opt_get_data(pkt_cookie_opt);
+ uint16_t pkt_cookie_len = knot_edns_opt_get_length(pkt_cookie_opt);
+ /* knot_edns_opt_cookie_parse() returns error on invalid data. */
+
+ const uint8_t *pkt_cc = NULL, *pkt_sc = NULL;
+ uint16_t pkt_cc_len = 0, pkt_sc_len = 0;
+
+ int ret = knot_edns_opt_cookie_parse(pkt_cookie_data, pkt_cookie_len,
+ &pkt_cc, &pkt_cc_len,
+ &pkt_sc, &pkt_sc_len);
+ if (ret != KNOT_EOK || !pkt_sc) {
+ VERBOSE_MSG(NULL, "%s\n",
+ "got malformed DNS cookie or server cookie missing");
+ return false;
+ }
+ if (kr_fails_assert(pkt_cc_len == KNOT_OPT_COOKIE_CLNT))
+ return false;
+
+ /* Check server address against received client cookie. */
+ const struct sockaddr *srvr_sockaddr = passed_server_sockaddr(req);
+ ret = srvr_sockaddr_cc_check(srvr_sockaddr, pkt_cc, pkt_cc_len,
+ clnt_sett);
+ if (ret < 0) {
+ VERBOSE_MSG(NULL, "%s\n", "could not match received cookie");
+ return false;
+ }
+ if (kr_fails_assert(srvr_sockaddr))
+ return false;
+
+ /* Don't cache received cookies that don't match the current secret. */
+ if ((ret == 1) &&
+ !is_cookie_cached(cache, srvr_sockaddr, pkt_cookie_opt)) {
+ ret = kr_cookie_lru_set(cache, srvr_sockaddr, pkt_cookie_opt);
+ if (ret != kr_ok()) {
+ VERBOSE_MSG(NULL, "%s\n", "failed caching cookie");
+ } else {
+ VERBOSE_MSG(NULL, "%s\n", "cookie cached");
+ }
+ }
+
+ return true;
+}
+
+/** Process incoming response. */
+int check_response(kr_layer_t *ctx, knot_pkt_t *pkt)
+{
+ struct kr_request *req = ctx->req;
+ struct kr_query *qry = req->current_query;
+ struct kr_cookie_ctx *cookie_ctx = &req->ctx->cookie_ctx;
+
+ if (ctx->state & (KR_STATE_DONE | KR_STATE_FAIL)) {
+ return ctx->state;
+ }
+
+ if (!cookie_ctx->clnt.enabled || (qry->flags.TCP)) {
+ return ctx->state;
+ }
+
+ /* Obtain cookie if present in response. Don't check actual content. */
+ uint8_t *pkt_cookie_opt = NULL;
+ if (knot_pkt_has_edns(pkt)) {
+ pkt_cookie_opt = knot_edns_get_option(pkt->opt_rr,
+ KNOT_EDNS_OPTION_COOKIE);
+ }
+
+ kr_cookie_lru_t *cookie_cache = req->ctx->cache_cookie;
+
+ const struct sockaddr *srvr_sockaddr = passed_server_sockaddr(req);
+
+ if (!pkt_cookie_opt && srvr_sockaddr &&
+ get_cookie_opt(cookie_cache, srvr_sockaddr)) {
+ /* We haven't received any cookies although we should. */
+ VERBOSE_MSG(NULL, "%s\n",
+ "expected to receive a cookie but none received");
+ return KR_STATE_FAIL;
+ }
+
+ if (!pkt_cookie_opt) {
+ /* Don't do anything if no cookies expected and received. */
+ return ctx->state;
+ }
+
+ if (!check_cookie_content_and_cache(&cookie_ctx->clnt, req,
+ pkt_cookie_opt, cookie_cache)) {
+ return KR_STATE_FAIL;
+ }
+
+ uint16_t rcode = knot_pkt_ext_rcode(pkt);
+ if (rcode == KNOT_RCODE_BADCOOKIE) {
+ struct kr_query *next = NULL;
+ if (!(qry->flags.BADCOOKIE_AGAIN)) {
+ /* Received first BADCOOKIE, regenerate query. */
+ next = kr_rplan_push(&req->rplan, qry->parent,
+ qry->sname, qry->sclass,
+ qry->stype);
+ }
+
+ if (next) {
+ VERBOSE_MSG(NULL, "%s\n", "BADCOOKIE querying again");
+ qry->flags.BADCOOKIE_AGAIN = true;
+ } else {
+ /*
+ * Either the planning of the second request failed or
+ * BADCOOKIE received for the second time.
+ *
+ * RFC7873 5.3 says that TCP should be used. Currently
+ * we always expect that the server doesn't support TCP.
+ */
+ qry->flags.BADCOOKIE_AGAIN = false;
+ return KR_STATE_FAIL;
+ }
+
+ return KR_STATE_CONSUME;
+ }
+
+ return ctx->state;
+}
+
+static inline uint8_t *req_cookie_option(struct kr_request *req)
+{
+ if (!req || !req->qsource.opt) {
+ return NULL;
+ }
+
+ return knot_edns_get_option(req->qsource.opt, KNOT_EDNS_OPTION_COOKIE);
+}
+
+/**
+ * @brief Returns resolver state and sets answer RCODE on missing or invalid
+ * server cookie.
+ *
+ * @note Caller should exit when only KR_STATE_FAIL is returned.
+ *
+ * @param state original resolver state
+ * @param sc_present true if server cookie is present
+ * @param ignore_badcookie true if bad cookies should be treated as good ones
+ * @param req request context
+ * @return new resolver state
+ */
+static int invalid_sc_status(int state, bool sc_present, bool ignore_badcookie,
+ const struct kr_request *req, knot_pkt_t *answer)
+{
+ if (kr_fails_assert(req && answer))
+ return KR_STATE_FAIL;
+
+ const knot_pkt_t *pkt = req->qsource.packet;
+
+ if (!pkt) {
+ return KR_STATE_FAIL;
+ }
+
+ if (knot_wire_get_qdcount(pkt->wire) == 0) {
+ /* RFC7873 5.4 */
+ state = KR_STATE_DONE;
+ if (sc_present) {
+ kr_pkt_set_ext_rcode(answer, KNOT_RCODE_BADCOOKIE);
+ state |= KR_STATE_FAIL;
+ }
+ } else if (!ignore_badcookie) {
+ /* Generate BADCOOKIE response. */
+ VERBOSE_MSG(NULL, "%s\n",
+ !sc_present ? "request is missing server cookie" :
+ "request has invalid server cookie");
+ if (!knot_pkt_has_edns(answer)) {
+ VERBOSE_MSG(NULL, "%s\n",
+ "missing EDNS section in prepared answer");
+ /* Caller should exit on this (and only this) state. */
+ return KR_STATE_FAIL;
+ }
+ kr_pkt_set_ext_rcode(answer, KNOT_RCODE_BADCOOKIE);
+ state = KR_STATE_FAIL | KR_STATE_DONE;
+ }
+
+ return state;
+}
+
+int check_request(kr_layer_t *ctx)
+{
+ struct kr_request *req = ctx->req;
+ struct kr_cookie_settings *srvr_sett = &req->ctx->cookie_ctx.srvr;
+
+ if (!srvr_sett->enabled) {
+ return ctx->state;
+ }
+
+ knot_pkt_t *answer = req->answer; // FIXME: see kr_request_ensure_answer()
+
+ if (ctx->state & (KR_STATE_DONE | KR_STATE_FAIL)) {
+ return ctx->state;
+ }
+
+ if (!srvr_sett->enabled) {
+ if (knot_pkt_has_edns(answer)) {
+ /* Delete any cookies. */
+ knot_edns_remove_options(answer->opt_rr,
+ KNOT_EDNS_OPTION_COOKIE);
+ }
+ return ctx->state;
+ }
+
+ uint8_t *req_cookie_opt = req_cookie_option(req);
+ if (!req_cookie_opt) {
+ return ctx->state; /* Don't do anything without cookies. */
+ }
+
+ struct knot_dns_cookies cookies;
+ memset(&cookies, 0, sizeof(cookies));
+ int ret = kr_parse_cookie_opt(req_cookie_opt, &cookies);
+ if (ret != kr_ok()) {
+ /* FORMERR -- malformed cookies. */
+ VERBOSE_MSG(NULL, "%s\n", "request with malformed cookie");
+ knot_wire_set_rcode(answer->wire, KNOT_RCODE_FORMERR);
+ return KR_STATE_FAIL | KR_STATE_DONE;
+ }
+
+ /*
+ * RFC7873 5.2.3 and 5.2.4 suggest that queries with invalid or
+ * missing server cookies can be treated like normal.
+ * Right now bad cookies are always ignored (i.e. treated as valid).
+ */
+ bool ignore_badcookie = true;
+
+ const struct knot_sc_alg *current_sc_alg = kr_sc_alg_get(srvr_sett->current.alg_id);
+
+ if (!req->qsource.addr || !srvr_sett->current.secr || !current_sc_alg) {
+ VERBOSE_MSG(NULL, "%s\n", "missing valid server cookie context");
+ return KR_STATE_FAIL;
+ }
+
+ int return_state = ctx->state;
+
+ struct knot_sc_private srvr_data = {
+ .clnt_sockaddr = req->qsource.addr,
+ .secret_data = srvr_sett->current.secr->data,
+ .secret_len = srvr_sett->current.secr->size
+ };
+
+ struct knot_sc_input sc_input = {
+ .cc = cookies.cc,
+ .cc_len = cookies.cc_len,
+ /* Don't set nonce here. */
+ .srvr_data = &srvr_data
+ };
+
+ struct kr_nonce_input nonce = {
+ .rand = kr_rand_bytes(sizeof(nonce.rand)),
+ .time = req->current_query->timestamp.tv_sec
+ };
+
+ if (!cookies.sc) {
+ /* Request has no server cookie. */
+ return_state = invalid_sc_status(return_state, false,
+ ignore_badcookie, req, answer);
+ if (return_state & KR_STATE_FAIL) {
+ return return_state;
+ }
+ goto answer_add_cookies;
+ }
+
+ /* Check server cookie obtained in request. */
+
+ ret = knot_sc_check(KR_NONCE_LEN, &cookies, &srvr_data, current_sc_alg);
+ if (ret == KNOT_EINVAL && srvr_sett->recent.secr) {
+ const struct knot_sc_alg *recent_sc_alg = kr_sc_alg_get(srvr_sett->recent.alg_id);
+ if (recent_sc_alg) {
+ /* Try recent algorithm. */
+ struct knot_sc_private recent_srvr_data = {
+ .clnt_sockaddr = req->qsource.addr,
+ .secret_data = srvr_sett->recent.secr->data,
+ .secret_len = srvr_sett->recent.secr->size
+ };
+ ret = knot_sc_check(KR_NONCE_LEN, &cookies,
+ &recent_srvr_data, recent_sc_alg);
+ }
+ }
+ if (ret != KNOT_EOK) {
+ /* Invalid server cookie. */
+ return_state = invalid_sc_status(return_state, true,
+ ignore_badcookie, req, answer);
+ if (return_state & KR_STATE_FAIL) {
+ return return_state;
+ }
+ goto answer_add_cookies;
+ }
+
+ /* Server cookie is OK. */
+
+answer_add_cookies:
+ /* Add server cookie into response. */
+ ret = kr_answer_write_cookie(&sc_input, &nonce, current_sc_alg, answer);
+ if (ret != kr_ok()) {
+ return_state = KR_STATE_FAIL;
+ }
+ return return_state;
+}
+
+#undef VERBOSE_MSG
diff --git a/modules/cookies/cookiemonster.h b/modules/cookies/cookiemonster.h
new file mode 100644
index 0000000..ab1fdeb
--- /dev/null
+++ b/modules/cookies/cookiemonster.h
@@ -0,0 +1,15 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libknot/packet/pkt.h>
+
+#include "lib/layer.h"
+
+/** Checks cookies of inbound requests. It's for kr_layer_api_t::begin. */
+int check_request(kr_layer_t *ctx);
+
+/** Checks cookies of received responses. It's for kr_layer_api_t::consume. */
+int check_response(kr_layer_t *ctx, knot_pkt_t *pkt);
diff --git a/modules/cookies/cookies.c b/modules/cookies/cookies.c
new file mode 100644
index 0000000..5b688d3
--- /dev/null
+++ b/modules/cookies/cookies.c
@@ -0,0 +1,78 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "daemon/engine.h"
+#include "lib/layer.h"
+#include "modules/cookies/cookiectl.h"
+#include "modules/cookies/cookiemonster.h"
+
+/**
+ * Get/set DNS cookie related stuff.
+ *
+ * Input: { name: value, ... }
+ * Output: current configuration
+ */
+static char *cookies_config(void *env, struct kr_module *module,
+ const char *args)
+{
+ struct kr_cookie_ctx *cookie_ctx = module->data;
+ if (kr_fails_assert(cookie_ctx))
+ return NULL;
+
+ /* Apply configuration, if any. */
+ config_apply(cookie_ctx, args);
+
+ /* Return current configuration. */
+ return config_read(cookie_ctx);
+}
+
+/*
+ * Module implementation.
+ */
+
+KR_EXPORT
+int cookies_init(struct kr_module *module)
+{
+ /* The function answer_finalize() in resolver is called before any
+ * .finish callback. Therefore this layer does not use it. */
+ static kr_layer_api_t layer = {
+ .begin = &check_request,
+ .consume = &check_response
+ };
+ /* Store module reference */
+ layer.data = module;
+ module->layer = &layer;
+
+ static const struct kr_prop props[] = {
+ { &cookies_config, "config", "Empty value to return current configuration.", },
+ { NULL, NULL, NULL }
+ };
+ module->props = props;
+
+ struct engine *engine = module->data;
+
+ struct kr_cookie_ctx *cookie_ctx = &engine->resolver.cookie_ctx;
+
+ int ret = config_init(cookie_ctx);
+ if (ret != kr_ok()) {
+ return ret;
+ }
+
+ /* Replace engine pointer. */
+ module->data = cookie_ctx;
+
+ return kr_ok();
+}
+
+KR_EXPORT
+int cookies_deinit(struct kr_module *module)
+{
+ struct kr_cookie_ctx *cookie_ctx = module->data;
+
+ config_deinit(cookie_ctx);
+
+ return kr_ok();
+}
+
+KR_MODULE_EXPORT(cookies)
diff --git a/modules/daf/.packaging/test.config b/modules/daf/.packaging/test.config
new file mode 100644
index 0000000..2fa1d8c
--- /dev/null
+++ b/modules/daf/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('daf')
+assert(daf)
+quit()
diff --git a/modules/daf/README.rst b/modules/daf/README.rst
new file mode 100644
index 0000000..a5e025e
--- /dev/null
+++ b/modules/daf/README.rst
@@ -0,0 +1,146 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-daf:
+
+DNS Application Firewall
+========================
+
+This module is a high-level interface for other powerful filtering modules and DNS views. It provides an easy interface to apply and monitor DNS filtering rules and a persistent memory for them. It also provides a restful service interface and an HTTP interface.
+
+Example configuration
+---------------------
+
+Firewall rules are declarative and consist of filters and actions. Filters have ``field operator operand`` notation (e.g. ``qname = example.com``), and may be chained using AND/OR keywords. Actions may or may not have parameters after the action name.
+
+.. code-block:: lua
+
+ -- Let's write some daft rules!
+ modules = { 'daf' }
+
+ -- Block all queries with QNAME = example.com
+ daf.add('qname = example.com deny')
+
+ -- Filters can be combined using AND/OR...
+ -- Block all queries with QNAME match regex and coming from given subnet
+ daf.add('qname ~ %w+.example.com AND src = 192.0.2.0/24 deny')
+
+ -- We also can reroute addresses in response to alternate target
+ -- This reroutes 192.0.2.1 to localhost
+ daf.add('src = 127.0.0.0/8 reroute 192.0.2.1-127.0.0.1')
+
+ -- Subnets work too, this reroutes a whole subnet
+ -- e.g. 192.0.2.55 to 127.0.0.55
+ daf.add('src = 127.0.0.0/8 reroute 192.0.2.0/24-127.0.0.0')
+
+ -- This rewrites all A answers for 'example.com' from
+ -- whatever the original address was to 127.0.0.2
+ daf.add('src = 127.0.0.0/8 rewrite example.com A 127.0.0.2')
+
+ -- Mirror queries matching given name to DNS logger
+ daf.add('qname ~ %w+.example.com mirror 127.0.0.2')
+ daf.add('qname ~ example-%d.com mirror 127.0.0.3@5353')
+
+ -- Forward queries from subnet
+ daf.add('src = 127.0.0.1/8 forward 127.0.0.1@5353')
+ -- Forward to multiple targets
+ daf.add('src = 127.0.0.1/8 forward 127.0.0.1@5353,127.0.0.2@5353')
+
+ -- Truncate queries based on destination IPs
+ daf.add('dst = 192.0.2.51 truncate')
+
+ -- Disable a rule
+ daf.disable(2)
+ -- Enable a rule
+ daf.enable(2)
+ -- Delete a rule
+ daf.del(2)
+
+ -- Delete all rules and start from scratch
+ daf.clear()
+
+.. warning:: Only the first matching rule's action is executed. Defining
+ additional actions for the same matching rule, e.g. ``src = 127.0.0.1/8``,
+ will have no effect.
+
+If you're not sure what firewall rules are in effect, see ``daf.rules``:
+
+.. code-block:: text
+
+ -- Show active rules
+ > daf.rules
+ [1] => {
+ [rule] => {
+ [count] => 42
+ [id] => 1
+ [cb] => function: 0x1a3eda38
+ }
+ [info] => qname = example.com AND src = 127.0.0.1/8 deny
+ [policy] => function: 0x1a3eda38
+ }
+ [2] => {
+ [rule] => {
+ [suspended] => true
+ [count] => 123522
+ [id] => 2
+ [cb] => function: 0x1a3ede88
+ }
+ [info] => qname ~ %w+.facebook.com AND src = 127.0.0.1/8 deny...
+ [policy] => function: 0x1a3ede88
+ }
+
+Web interface
+-------------
+
+If you have :ref:`HTTP/2 <mod-http>` loaded, the firewall automatically loads as a snippet.
+You can create, track, suspend and remove firewall rules from the web interface.
+If you load both modules, you have to load `daf` after `http`.
+
+RESTful interface
+-----------------
+
+The module also exports a RESTful API for operations over rule chains.
+
+
+.. csv-table::
+ :header: "URL", "HTTP Verb", "Action"
+
+ "/daf", "GET", "Return JSON list of active rules."
+ "/daf", "POST", "Insert new rule, rule string is expected in body. Returns rule information in JSON."
+ "/daf/<id>", "GET", "Retrieve a rule matching given ID."
+ "/daf/<id>", "DELETE", "Delete a rule matching given ID."
+ "/daf/<id>/<prop>/<val>", "PATCH", "Modify given rule, for example /daf/3/active/false suspends rule 3."
+
+This interface is used by the web interface for all operations, but you can also use it directly
+for testing.
+
+.. code-block:: bash
+
+ # Get current rule set
+ $ curl -s -X GET http://localhost:8453/daf | jq .
+ {}
+
+ # Create new rule
+ $ curl -s -X POST -d "src = 127.0.0.1 pass" http://localhost:8453/daf | jq .
+ {
+ "count": 0,
+ "active": true,
+ "info": "src = 127.0.0.1 pass",
+ "id": 1
+ }
+
+ # Disable rule
+ $ curl -s -X PATCH http://localhost:8453/daf/1/active/false | jq .
+ true
+
+ # Retrieve a rule information
+ $ curl -s -X GET http://localhost:8453/daf/1 | jq .
+ {
+ "count": 4,
+ "active": true,
+ "info": "src = 127.0.0.1 pass",
+ "id": 1
+ }
+
+ # Delete a rule
+ $ curl -s -X DELETE http://localhost:8453/daf/1 | jq .
+ true
diff --git a/modules/daf/daf.js b/modules/daf/daf.js
new file mode 100644
index 0000000..05b171b
--- /dev/null
+++ b/modules/daf/daf.js
@@ -0,0 +1,295 @@
+/* Filter grammar
+ * SPDX-License-Identifier: GPL-3.0-or-later */
+const dafg = {
+ key: {'qname': true, 'src': true, 'dst': true},
+ op: {'=': true, '~': true},
+ conj: {'and': true, 'or': true},
+ action: {'pass': true, 'deny': true, 'drop': true, 'truncate': true, 'forward': true, 'reroute': true, 'rewrite': true, 'mirror': true},
+ suggest: [
+ 'QNAME = example.com',
+ 'QNAME ~ %d+.example.com',
+ 'SRC = 127.0.0.1',
+ 'SRC = 127.0.0.1/8',
+ 'DST = 127.0.0.1',
+ 'DST = 127.0.0.1/8',
+ /* Action examples */
+ 'PASS', 'DENY', 'DROP', 'TRUNCATE',
+ 'FORWARD 127.0.0.1',
+ 'MIRROR 127.0.0.1',
+ 'REROUTE 127.0.0.1-192.168.1.1',
+ 'REROUTE 127.0.0.1/24-192.168.1.0',
+ 'REWRITE example.com A 127.0.0.1',
+ 'REWRITE example.com AAAA ::1',
+ ]
+};
+
+function setValidateHint(cls) {
+ var builderForm = $('#daf-builder-form');
+ builderForm.removeClass('has-error has-warning has-success');
+ if (cls) {
+ builderForm.addClass(cls);
+ }
+}
+
+function validateToken(tok, tbl) {
+ if (tok.length > 0 && tok[0].length > 0) {
+ if (tbl[tok[0].toLowerCase()]) {
+ setValidateHint('has-success');
+ return true;
+ } else { setValidateHint('has-error'); }
+ } else { setValidateHint('has-warning'); }
+ return false;
+}
+
+function parseOption(tok) {
+ var key = tok.shift().toLowerCase();
+ var op = null;
+ if (dafg.key[key]) {
+ op = tok.shift();
+ if (op) {
+ op = op.toLowerCase();
+ }
+ }
+ const item = {
+ text: key.toUpperCase() + ' ' + (op ? op.toUpperCase() : '') + ' ' + tok.join(' '),
+ };
+ if (dafg.key[key]) {
+ item.class = 'tag-default';
+ } else if (dafg.action[key]) {
+ item.class = 'tag-warning';
+ } else if (dafg.conj[key]) {
+ item.class = 'tag-success';
+ }
+ return item;
+}
+
+function createOption(input) {
+ const item = parseOption(input.split(' '));
+ item.value = input;
+ return item;
+}
+
+function dafComplete(form) {
+ const items = form.items;
+ for (var i in items) {
+ const tok = items[i].split(' ')[0].toLowerCase();
+ if (dafg.action[tok]) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function formatRule(input) {
+ const tok = input.split(' ');
+ var res = [];
+ while (tok.length > 0) {
+ const key = tok.shift().toLowerCase();
+ if (dafg.key[key]) {
+ var item = parseOption([key, tok.shift(), tok.shift()]);
+ res.push('<span class="label tag '+item.class+'">'+item.text+'</span>');
+ } else if (dafg.action[key]) {
+ var item = parseOption([key].concat(tok));
+ res.push('<span class="label tag '+item.class+'">'+item.text+'</span>');
+ tok.splice(0, tok.length);
+ } else if (dafg.conj[key]) {
+ var item = parseOption([key]);
+ res.push('<span class="label tag '+item.class+'">'+item.text+'</span>');
+ }
+ }
+ return res.join('');
+}
+
+function toggleRule(row, span, enabled) {
+ if (!enabled) {
+ span.removeClass('glyphicon-pause');
+ span.addClass('glyphicon-play');
+ row.addClass('warning');
+ } else {
+ span.removeClass('glyphicon-play');
+ span.addClass('glyphicon-pause');
+ row.removeClass('warning');
+ }
+}
+
+function ruleControl(cell, type, url, action) {
+ const row = cell.parent();
+ $.ajax({
+ url: 'daf/' + row.data('rule-id') + url,
+ type: type,
+ success: action,
+ error: function (data) {
+ row.show();
+ const reason = data.responseText.length > 0 ? data.responseText : 'internal error';
+ cell.find('.alert').remove();
+ cell.append(
+ '<div class="alert alert-danger" role="alert">'+
+ 'Failed (code: '+data.status+', reason: '+reason+').'+
+ '</div>'
+ );
+ },
+ });
+}
+
+function bindRuleControl(cell) {
+ const row = cell.parent();
+ cell.find('.daf-remove').click(function() {
+ row.hide();
+ ruleControl(cell, 'DELETE', '', function (data) {
+ cell.parent().remove();
+ });
+ });
+ cell.find('.daf-suspend').click(function() {
+ const span = $(this).find('span');
+ ruleControl(cell, 'PATCH', span.hasClass('glyphicon-pause') ? '/active/false' : '/active/true');
+ toggleRule(row, span, span.hasClass('glyphicon-play'));
+ });
+}
+
+function loadRule(rule, tbl) {
+ const row = $('<tr data-rule-id="'+rule.id+'" />');
+ row.append('<td class="daf-rule">' + formatRule(rule.info) + '</td>');
+ row.append('<td class="daf-count">' + rule.count + '</td>');
+ row.append('<td class="daf-rate"><span class="badge"></span></td>');
+ row.append('<td class="daf-ctl text-right">' +
+ '<div class="btn-group btn-group-xs">' +
+ '<button class="btn btn-default daf-suspend"><span class="glyphicon" aria="hidden" /></button>' +
+ '<button class="btn btn-default daf-remove"><span class="glyphicon glyphicon-remove" aria="hidden" /></button>' +
+ '</div></td>');
+ tbl.append(row);
+ /* Bind rule controls */
+ bindRuleControl(row.find('.daf-ctl'));
+ toggleRule(row, row.find('.daf-suspend span'), rule.active);
+}
+
+/* Load the filter table from JSON */
+function loadTable(resp) {
+ const tbl = $('#daf-rules')
+ tbl.children().remove();
+ tbl.append('<tr><th>Rule</th><th>Matches</th><th>Rate</th><th></th></tr>')
+ for (var i in resp) {
+ loadRule(resp[i], tbl);
+ }
+}
+
+document.addEventListener("DOMContentLoaded", () => {
+ /* Load the filter table. */
+ $.ajax({
+ url: 'daf',
+ type: 'get',
+ dataType: 'json',
+ success: loadTable
+ });
+ /* Listen for counter updates */
+ const wsStats = ('https:' == document.location.protocol ? 'wss://' : 'ws://') + location.host + '/daf';
+ const ws = new Socket(wsStats);
+ var lastRateUpdate = Date.now();
+ ws.onmessage = function(evt) {
+ var data = JSON.parse(evt.data);
+ /* Update heartbeat clock */
+ var now = Date.now();
+ var dt = now - lastRateUpdate;
+ lastRateUpdate = now;
+ /* Update match counts and rates */
+ $('#daf-rules .daf-rate span').text('');
+ for (var key in data) {
+ const row = $('tr[data-rule-id="'+key+'"]');
+ if (row) {
+ const cell = row.find('.daf-count');
+ const diff = data[key] - parseInt(cell.text());
+ cell.text(data[key]);
+ const badge = row.find('.daf-rate span');
+ if (diff > 0) {
+ /* Normalize difference to heartbeat (in msecs) */
+ const rate = Math.ceil((1000 * diff) / dt);
+ badge.text(rate + ' pps');
+ }
+ }
+ }
+ };
+ /* Rule builder UI */
+ $('#daf-builder').selectize({
+ delimiter: ',',
+ persist: true,
+ highlight: true,
+ closeAfterSelect: true,
+ onItemAdd: function (input, item) {
+ setValidateHint();
+ /* Prevent new rules when action is specified */
+ const tok = input.split(' ');
+ if (dafg.action[tok[0].toLowerCase()]) {
+ $('#daf-add').focus();
+ } else if(dafComplete(this)) {
+ /* No more rules after query is complete. */
+ item.remove();
+ }
+ },
+ createFilter: function (input) {
+ const tok = input.split(' ');
+ var key, op, expr;
+ /* If there are already filters, allow conjunctions. */
+ if (tok.length > 0 && this.items.length > 0 && dafg.conj[tok[0]]) {
+ setValidateHint();
+ return true;
+ }
+ /* First token is expected to be filter key,
+ * or any postrule with a parameter */
+ if (validateToken(tok, dafg.key)) {
+ key = tok.shift();
+ } else if (tok.length > 1 && validateToken(tok, dafg.action)) {
+ setValidateHint();
+ return true;
+ } else {
+ return false;
+ }
+ /* Input is a filter - second token must be operator */
+ if (validateToken(tok, dafg.op)) {
+ op = tok.shift();
+ } else {
+ return false;
+ }
+ /* Input is a filter - the rest of the tokens are RHS arguments. */
+ if (tok.length > 0 && tok[0].length > 0) {
+ expr = tok.join(' ');
+ } else {
+ setValidateHint('has-warning');
+ return false;
+ }
+ setValidateHint('has-success');
+ return true;
+ },
+ create: createOption,
+ render: {
+ item: function(item, escape) {
+ return '<div class="name '+item.class+'">' + escape(item.text) + '</span>';
+ },
+ },
+ });
+ /* Add default suggestions. */
+ const dafBuilder = $('#daf-builder')[0].selectize;
+ for (var i in dafg.suggest) {
+ dafBuilder.addOption(createOption(dafg.suggest[i]));
+ }
+ /* Rule builder submit */
+ $('#daf-add').click(function () {
+ const form = $('#daf-builder-form').parent();
+ if (dafBuilder.items.length == 0 || form.hasClass('has-error')) {
+ return;
+ }
+ /* Clear previous errors and resubmit. */
+ form.parent().find('.alert').remove();
+ $.post('daf', dafBuilder.items.join(' '))
+ .done(function (data) {
+ dafBuilder.clear();
+ loadRule(data, $('#daf-rules'));
+ })
+ .fail(function (data) {
+ const reason = data.responseText.length > 0 ? data.responseText : 'internal error';
+ form.after(
+ '<div class="alert alert-danger" role="alert">'+
+ 'Couldn\'t add rule (code: '+data.status+', reason: '+reason+').'+
+ '</div>'
+ );
+ });
+ });
+});
diff --git a/modules/daf/daf.lua b/modules/daf/daf.lua
new file mode 100644
index 0000000..c3b089b
--- /dev/null
+++ b/modules/daf/daf.lua
@@ -0,0 +1,392 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+
+local ffi = require('ffi')
+
+-- Load dependent modules
+if not view then modules.load('view') end
+if not policy then modules.load('policy') end
+
+-- Actions
+local actions = {
+ pass = function() return policy.PASS end,
+ deny = function () return policy.DENY end,
+ drop = function() return policy.DROP end,
+ tc = function() return policy.TC end,
+ truncate = function() return policy.TC end,
+ forward = function (g)
+ local addrs = {}
+ local tok = g()
+ for addr in string.gmatch(tok, '[^,]+') do
+ table.insert(addrs, addr)
+ end
+ return policy.FORWARD(addrs)
+ end,
+ mirror = function (g)
+ return policy.MIRROR(g())
+ end,
+ reroute = function (g)
+ local rules = {}
+ local tok = g()
+ while tok do
+ local from, to = tok:match '([^-]+)-(%S+)'
+ rules[from] = to
+ tok = g()
+ end
+ return policy.REROUTE(rules)
+ end,
+ rewrite = function (g)
+ local rules = {}
+ local tok = g()
+ while tok do
+ -- This is currently limited to A/AAAA rewriting
+ -- in fixed format '<owner> <type> <addr>'
+ local _, to = g(), g()
+ rules[tok] = to
+ tok = g()
+ end
+ return policy.REROUTE(rules, true)
+ end,
+}
+
+-- Filter rules per column
+local filters = {
+ -- Filter on QNAME (either pattern or suffix match)
+ qname = function (g)
+ local op, val = g(), todname(g())
+ if op == '~' then return policy.pattern(true, val:sub(2)) -- Skip leading label length
+ elseif op == '=' then return policy.suffix(true, {val})
+ else error(string.format('invalid operator "%s" on qname', op)) end
+ end,
+ -- Filter on source address
+ src = function (g)
+ local op = g()
+ if op ~= '=' then error('address supports only "=" operator') end
+ return view.rule_src(true, g())
+ end,
+ -- Filter on destination address
+ dst = function (g)
+ local op = g()
+ if op ~= '=' then error('address supports only "=" operator') end
+ return view.rule_dst(true, g())
+ end,
+}
+
+local function parse_filter(tok, g, prev)
+ if not tok then error(string.format('expected filter after "%s"', prev)) end
+ local filter = filters[tok:lower()]
+ if not filter then error(string.format('invalid filter "%s"', tok)) end
+ return filter(g)
+end
+
+local function parse_rule(g)
+ -- Allow action without filter
+ local tok = g()
+ if tok == nil then
+ error('empty rule is not allowed')
+ end
+ if not filters[tok:lower()] then
+ return tok, nil
+ end
+ local f = parse_filter(tok, g)
+ -- Compose filter functions on conjunctions
+ -- or terminate filter chain and return
+ tok = g()
+ while tok do
+ if tok:lower() == 'and' then
+ local fa, fb = f, parse_filter(g(), g, tok)
+ f = function (req, qry) return fa(req, qry) and fb(req, qry) end
+ elseif tok:lower() == 'or' then
+ local fa, fb = f, parse_filter(g(), g, tok)
+ f = function (req, qry) return fa(req, qry) or fb(req, qry) end
+ else
+ break
+ end
+ tok = g()
+ end
+ return tok, f
+end
+
+local function parse_query(g)
+ local ok, actid, filter = pcall(parse_rule, g)
+ if not ok then return nil, actid end
+ actid = actid:lower()
+ if not actions[actid] then return nil, string.format('invalid action "%s"', actid) end
+ -- Parse and interpret action
+ local action = actions[actid]
+ if type(action) == 'function' then
+ action = action(g)
+ end
+ return actid, action, filter
+end
+
+-- Compile a rule described by query language
+-- The query language is modelled by iptables/nftables
+-- conj = AND | OR
+-- op = IS | NOT | LIKE | IN
+-- filter = <key> <op> <expr>
+-- rule = <filter> | <filter> <conj> <rule>
+-- action = PASS | DENY | DROP | TC | FORWARD
+-- query = <rule> <action>
+local function compile(query)
+ local g = string.gmatch(query, '%S+')
+ return parse_query(g)
+end
+
+-- @function Describe given rule for presentation
+local function rule_info(r)
+ return {info=r.info, id=r.rule.id, active=(r.rule.suspended ~= true), count=r.rule.count}
+end
+
+-- Module declaration
+local M = {
+ rules = {}
+}
+
+-- @function Remove a rule
+
+-- @function Cleanup module
+function M.deinit()
+ if http then
+ local endpoints = http.configs._builtin.webmgmt.endpoints
+ endpoints['/daf'] = nil
+ endpoints['/daf.js'] = nil
+ http.snippets['/daf'] = nil
+ end
+end
+
+-- @function Add rule
+function M.add(rule)
+ -- Ignore duplicates
+ for _, r in ipairs(M.rules) do
+ if r.info == rule then return r end
+ end
+ local id, action, filter = compile(rule)
+ if not id then error(action) end
+ -- Combine filter and action into policy
+ local p
+ if filter then
+ p = function (req, qry)
+ return filter(req, qry) and action
+ end
+ else
+ p = function ()
+ return action
+ end
+ end
+ local desc = {info=rule, policy=p}
+ -- Enforce in policy module, special actions are postrules
+ if id == 'reroute' or id == 'rewrite' then
+ desc.rule = policy.add(p, true)
+ else
+ desc.rule = policy.add(p)
+ end
+ table.insert(M.rules, desc)
+ return desc
+end
+
+-- @function Remove a rule
+function M.del(id)
+ for key, r in ipairs(M.rules) do
+ if r.rule.id == id then
+ policy.del(id)
+ table.remove(M.rules, key)
+ return true
+ end
+ end
+ return nil
+end
+
+-- @function Remove all rules
+function M.clear()
+ for _, r in ipairs(M.rules) do
+ policy.del(r.rule.id)
+ end
+ M.rules = {}
+ return true
+end
+
+-- @function Find a rule
+function M.get(id)
+ for _, r in ipairs(M.rules) do
+ if r.rule.id == id then
+ return r
+ end
+ end
+ return nil
+end
+
+-- @function Enable/disable a rule
+function M.toggle(id, val)
+ for _, r in ipairs(M.rules) do
+ if r.rule.id == id then
+ r.rule.suspended = not val
+ return true
+ end
+ end
+ return nil
+end
+
+-- @function Enable/disable a rule
+function M.disable(id)
+ return M.toggle(id, false)
+end
+function M.enable(id)
+ return M.toggle(id, true)
+end
+
+local function consensus(op, ...)
+ local results = map(string.format(op, ...))
+ 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
+
+-- @function Public-facing API
+local function api(h, stream)
+ local m = h:get(':method')
+ -- GET method
+ if m == 'GET' then
+ local path = h:get(':path')
+ local id = tonumber(path:match '/([^/]*)$')
+ if id then
+ local r = M.get(id)
+ if r then
+ return rule_info(r)
+ end
+ return 404, '"No such rule"' -- Not found
+ else
+ local ret = {}
+ for _, r in ipairs(M.rules) do
+ table.insert(ret, rule_info(r))
+ end
+ return ret
+ end
+ -- DELETE method
+ elseif m == 'DELETE' then
+ local path = h:get(':path')
+ local id = tonumber(path:match '/([^/]*)$')
+ if id then
+ if consensus('daf.del(%s)', id) then
+ return tojson(true)
+ end
+ return 404, '"No such rule"' -- Not found
+ end
+ return 400 -- Request doesn't have numeric id
+ -- POST method
+ elseif m == 'POST' then
+ local query = stream:get_body_as_string()
+ 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:
+ -- 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
+ -- PATCH method
+ elseif m == 'PATCH' then
+ local path = h:get(':path')
+ local id, action, val = path:match '(%d+)/([^/]*)/([^/]*)$'
+ id = tonumber(id)
+ if not id or not action or not val then
+ return 400 -- Request not well formatted
+ end
+ -- We do not support more actions
+ if action == 'active' then
+ if consensus('daf.toggle(%d, %s)', id, val == 'true' or 'false') then
+ return tojson(true)
+ else
+ return 404, '"No such rule"'
+ end
+ else
+ return 501, '"Action not implemented"'
+ end
+ end
+end
+
+local function getmatches()
+ local update = {}
+ -- 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
+end
+
+-- @function Publish DAF statistics
+local function publish(_, ws)
+ local ok, last = true, nil
+ while ok do
+ -- Check if we have new rule matches
+ local diff = {}
+ local has_update, update = pcall(getmatches)
+ if has_update then
+ if last then
+ for id, count in pairs(update) do
+ if not last[id] or last[id] < count then
+ diff[id] = count
+ end
+ end
+ end
+ last = update
+ end
+ -- Update counters when there is a new data
+ if next(diff) ~= nil then
+ ok = ws:send(tojson(diff))
+ else
+ ok = ws:send_ping()
+ end
+ worker.sleep(1)
+ end
+end
+
+function M.init()
+ -- avoid ordering problem between HTTP and daf module
+ event.after(0, M.config)
+end
+
+-- @function Configure module
+function M.config()
+ if not http then
+ log_warn(ffi.C.LOG_GRP_DAF,
+ 'HTTP API unavailable because HTTP module is not loaded, use modules.load("http")')
+ return
+ end
+ local endpoints = http.configs._builtin.webmgmt.endpoints
+ -- Export API and data publisher
+ endpoints['/daf.js'] = http.page('daf.js', 'daf')
+ endpoints['/daf'] = {'application/json', api, publish}
+ -- Export snippet
+ http.snippets['/daf'] = {'Application Firewall', [[
+ <script type="text/javascript" src="daf.js"></script>
+ <div class="row" style="margin-bottom: 5px">
+ <form id="daf-builder-form">
+ <div class="col-md-11">
+ <input type="text" id="daf-builder" class="form-control" aria-label="..." />
+ </div>
+ <div class="col-md-1">
+ <button type="button" id="daf-add" class="btn btn-default btn-sm">Add</button>
+ </div>
+ </form>
+ </div>
+ <div class="row">
+ <div class="col-md-12">
+ <table id="daf-rules" class="table table-striped table-responsive">
+ <th><td>No rules here yet.</td></th>
+ </table>
+ </div>
+ </div>
+ ]]}
+end
+
+return M
diff --git a/modules/daf/daf.test.lua b/modules/daf/daf.test.lua
new file mode 100644
index 0000000..2a46393
--- /dev/null
+++ b/modules/daf/daf.test.lua
@@ -0,0 +1,80 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+
+-- 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 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')
+
+hints['pass.'] = '127.0.0.1'
+hints['deny.'] = '127.0.0.1'
+hints['deny.'] = '127.0.0.1'
+hints['drop.'] = '127.0.0.1'
+hints['del.'] = '127.0.0.1'
+hints['del2.'] = '127.0.0.1'
+hints['toggle.'] = '127.0.0.1'
+
+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)
+ check_answer('daf sanity (no rules)', 'deny.', kres.type.A, kres.rcode.NOERROR)
+ check_answer('daf sanity (no rules)', 'drop.', kres.type.A, kres.rcode.NOERROR)
+ check_answer('daf sanity (no rules)', 'del.', kres.type.A, kres.rcode.NOERROR)
+ check_answer('daf sanity (no rules)', 'del2.', kres.type.A, kres.rcode.NOERROR)
+ check_answer('daf sanity (no rules)', 'toggle.', kres.type.A, kres.rcode.NOERROR)
+end
+
+local function test_basic_actions()
+ daf.add('qname = pass. pass')
+ daf.add('qname = deny. deny')
+ daf.add('qname = drop. drop')
+
+ check_answer('daf pass action', 'pass.', kres.type.A, kres.rcode.NOERROR)
+ check_answer('daf deny action', 'deny.', kres.type.A, kres.rcode.NXDOMAIN)
+ check_answer('daf drop action', 'drop.', kres.type.A, kres.rcode.SERVFAIL)
+end
+
+local function test_del()
+ -- first matching rule is used
+ local first = daf.add('qname = del. deny')
+ local second = daf.add('qname = del2. deny')
+
+ check_answer('daf del - first rule active',
+ 'del.', kres.type.A, kres.rcode.NXDOMAIN)
+ check_answer('daf del - second rule active',
+ 'del2.', kres.type.A, kres.rcode.NXDOMAIN)
+ daf.del(first.rule.id)
+ check_answer('daf del - first rule deleted',
+ 'del.', kres.type.A, kres.rcode.NOERROR)
+ daf.del(second.rule.id)
+ check_answer('daf del - second rule deleted',
+ 'del2.', kres.type.A, kres.rcode.NOERROR)
+end
+
+local function test_toggle()
+ local toggle = daf.add('qname = toggle. deny')
+
+ check_answer('daf - toggle active',
+ 'toggle.', kres.type.A, kres.rcode.NXDOMAIN)
+ daf.disable(toggle.rule.id)
+ check_answer('daf - toggle disabled',
+ 'toggle.', kres.type.A, kres.rcode.NOERROR)
+ daf.enable(toggle.rule.id)
+ check_answer('daf - toggle enabled',
+ 'toggle.', kres.type.A, kres.rcode.NXDOMAIN)
+end
+
+return {
+ test_sanity, -- must be first, expects no daf rules
+ test_basic_actions,
+ test_del,
+ test_toggle,
+}
diff --git a/modules/daf/daf_http.test.lua b/modules/daf/daf_http.test.lua
new file mode 100644
index 0000000..20d5f90
--- /dev/null
+++ b/modules/daf/daf_http.test.lua
@@ -0,0 +1,216 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- check prerequisites
+local has_http = pcall(require, 'kres_modules.http') and pcall(require, 'http.request')
+if not has_http then
+ -- 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')
+ modules.load('daf')
+
+ local bound
+ for _ = 1,1000 do
+ bound, _err = pcall(net.listen, '127.0.0.1', math.random(40000, 49999), { kind = 'webmgmt'})
+ if bound then
+ break
+ end
+ end
+ assert(bound, 'unable to bind a port for HTTP module (1000 attempts)')
+
+ -- globals for this module
+ local _, host, port, baseuri
+ local function start_server()
+ local server_fd = next(http.servers)
+ assert(server_fd)
+ local server = http.servers[server_fd].server
+ ok(server ~= nil, 'creates server instance')
+ _, host, port = server:localname()
+ ok(host and port, 'binds to an interface')
+ baseuri = string.format('http://%s:%d/daf', host, port)
+ end
+
+ -- helper for returning useful values to test on
+-- local function http_get(uri)
+-- local headers, stream = assert(request.new_from_uri(uri):go(16))
+-- local body = assert(stream:get_body_as_string())
+-- return tonumber(headers:get(':status')), body, headers:get('content-type')
+-- end
+
+ local function http_req(uri, method, reqbody)
+ local req = assert(request.new_from_uri(baseuri .. uri))
+ req.headers:upsert(':method', method)
+ req:set_body(reqbody)
+ local headers, stream = assert(req:go(16))
+ local ansbody = assert(stream:get_body_as_string())
+ return tonumber(headers:get(':status')), ansbody, headers:get('content-type')
+ end
+
+ local function http_get(uri)
+ return http_req(uri, 'GET')
+ end
+
+ -- compare two tables, expected value is specified as JSON
+ -- comparison relies on table_print which sorts table keys
+ local function compare_tables(expectedjson, gotjson, desc)
+ same(
+ table_print(fromjson(expectedjson)),
+ table_print(fromjson(gotjson)),
+ desc)
+ end
+
+ -- test whether http interface responds and binds
+ local function test_daf_api()
+ local code, body, mime
+ -- rule listing /daf
+ code, body, mime = http_get('/')
+ same(code, 200, 'rule listing return 200 OK')
+ same(body, '{}', 'daf rule list is empty after start')
+ same(mime, 'application/json', 'daf rule list has application/json content type')
+ -- get non-existing rule
+ code, body = http_req('/0', 'GET')
+ same(code, 404, 'non-existing rule retrieval returns 404')
+ same(body, '"No such rule"', 'explanatory message is present')
+
+ -- delete non-existing rule
+ code, body = http_req('/0', 'DELETE')
+ same(code, 404, 'non-existing rule deletion returns 404')
+ same(body, '"No such rule"', 'explanatory message is present')
+
+ -- bad PATCH
+ code = http_req('/0', 'PATCH')
+ same(code, 400, 'PATCH detects missing parameters')
+
+ -- bad POST
+ code = http_req('/', 'POST')
+ same(code, 500, 'POST without parameters is detected')
+
+ -- POST first new rule
+ code, body, mime = http_req('/', 'POST', 'src = 192.0.2.0 pass')
+ same(code, 200, 'first POST succeeds')
+ compare_tables(body,
+ '{"count":0,"active":true,"id":0,"info":"src = 192.0.2.0 pass"}',
+ 'POST returns new rule in JSON')
+ same(mime, 'application/json', 'rule has application/json content type')
+
+ -- GET first rule
+ code, body, mime = http_req('/0', 'GET')
+ same(code, 200, 'GET for first rule succeeds')
+ compare_tables(body,
+ '{"count":0,"active":true,"id":0,"info":"src = 192.0.2.0 pass"}',
+ 'POST returns new rule in JSON')
+ same(mime, 'application/json', 'rule has application/json content type')
+
+ -- POST second new rule
+ code, body, mime = http_req('/', 'POST', 'src = 192.0.2.1 pass')
+ same(code, 200, 'second POST succeeds')
+ compare_tables(body,
+ '{"count":0,"active":true,"id":1,"info":"src = 192.0.2.1 pass"}',
+ 'POST returns new rule in JSON')
+ same(mime, 'application/json', 'rule has application/json content type')
+
+ -- GET second rule
+ code, body, mime = http_req('/1', 'GET')
+ same(code, 200, 'GET for second rule succeeds')
+ compare_tables(body,
+ '{"count":0,"active":true,"id":1,"info":"src = 192.0.2.1 pass"}',
+ 'POST returns new rule in JSON')
+ same(mime, 'application/json', 'rule has application/json content type')
+
+ -- PATCH first rule
+ code, body, mime = http_req('/0/active/false', 'PATCH')
+ same(code, 200, 'PATCH for first rule succeeds')
+ same(body, 'true', 'PATCH returns success in body')
+ same(mime, 'application/json', 'PATCH return value has application/json content type')
+
+ -- GET modified first rule
+ code, body, mime = http_req('/0', 'GET')
+ same(code, 200, 'GET for first rule succeeds')
+ compare_tables(body,
+ '{"count":0,"active":false,"id":0,"info":"src = 192.0.2.0 pass"}',
+ 'GET returns modified rule in JSON')
+ same(mime, 'application/json', 'rule has application/json content type')
+
+ -- GET both rules
+ code, body, mime = http_req('/', 'GET')
+ same(code, 200, 'GET for both rule succeeds')
+ compare_tables(body, [[
+ [
+ {"count":0,"active":false,"info":"src = 192.0.2.0 pass","id":0},
+ {"count":0,"active":true,"info":"src = 192.0.2.1 pass","id":1}]
+ ]],
+ 'GET returns both rules in JSON including modifications')
+ same(mime, 'application/json', 'rule list has application/json content type')
+
+ -- PATCH first rule back to original state
+ code, body, mime = http_req('/0/active/true', 'PATCH')
+ same(code, 200, 'PATCH for first rule succeeds')
+ same(body, 'true', 'PATCH returns success in body')
+ same(mime, 'application/json', 'PATCH return value has application/json content type')
+
+ -- GET modified (reversed) first rule
+ code, body, mime = http_req('/0', 'GET')
+ same(code, 200, 'GET for first rule succeeds')
+ compare_tables(body,
+ '{"count":0,"active":true,"id":0,"info":"src = 192.0.2.0 pass"}',
+ 'GET returns modified rule in JSON')
+ same(mime, 'application/json', 'rule has application/json content type')
+
+ -- DELETE first rule
+ code, body, mime = http_req('/0', 'DELETE')
+ same(code, 200, 'DELETE for first rule succeeds')
+ same(body, 'true', 'DELETE returns success in body')
+ same(mime, 'application/json', 'DELETE return value has application/json content type')
+
+ -- GET deleted (first) rule
+ code, body = http_req('/0', 'GET')
+ same(code, 404, 'GET for deleted fails with 404')
+ same(body, '"No such rule"', 'failed GET contains explanatory message')
+
+ -- GET second rule
+ code, body, mime = http_req('/1', 'GET')
+ same(code, 200, 'GET for second rule still succeeds')
+ compare_tables(body,
+ '{"count":0,"active":true,"id":1,"info":"src = 192.0.2.1 pass"}',
+ 'POST returns new rule in JSON')
+ same(mime, 'application/json', 'rule has application/json content type')
+
+ -- GET list of all rules
+ code, body, mime = http_req('/', 'GET')
+ same(code, 200, 'GET returns list with the remaining rule')
+ compare_tables(body,
+ '[{"count":0,"active":true,"id":1,"info":"src = 192.0.2.1 pass"}]',
+ 'rule list contains only the remaining rule in JSON')
+ same(mime, 'application/json', 'rule has application/json content type')
+
+ -- try to DELETE first rule again
+ code, body = http_req('/0', 'DELETE')
+ same(code, 404, 'DELETE for already deleted rule fails with 404')
+ same(body, '"No such rule"', 'DELETE explains failure')
+
+ -- DELETE second rule
+ code, body, mime = http_req('/1', 'DELETE')
+ same(code, 200, 'DELETE for second rule succeeds')
+ same(body, 'true', 'DELETE returns success in body')
+ same(mime, 'application/json', 'DELETE return value has application/json content type')
+
+ -- GET (supposedly empty) list of all rules
+ code, body, mime = http_req('/', 'GET')
+ same(code, 200, 'GET returns list with the remaining rule')
+ compare_tables(body, '[]', 'rule list is now empty JSON list')
+ same(mime, 'application/json', 'rule has application/json content type')
+ end
+
+ -- plan tests
+ local tests = {
+ start_server,
+ test_daf_api,
+ }
+
+ return tests
+end
diff --git a/modules/daf/meson.build b/modules/daf/meson.build
new file mode 100644
index 0000000..c46b749
--- /dev/null
+++ b/modules/daf/meson.build
@@ -0,0 +1,21 @@
+# LUA module: daf
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+config_tests += [
+ ['daf', files('daf.test.lua')],
+ ['daf_http', files('daf_http.test.lua')],
+]
+
+integr_tests += [
+ ['daf', meson.current_source_dir() / 'test.integr'],
+]
+
+lua_mod_src += [
+ files('daf.lua'),
+]
+
+# install daf.js
+install_data(
+ 'daf.js',
+ install_dir: modules_dir / 'daf',
+)
diff --git a/modules/daf/test.integr/deckard.yaml b/modules/daf/test.integr/deckard.yaml
new file mode 100644
index 0000000..455086f
--- /dev/null
+++ b/modules/daf/test.integr/deckard.yaml
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+programs:
+- name: kresd
+ binary: kresd
+ additional:
+ - --noninteractive
+ templates:
+ - modules/daf/test.integr/kresd_config.j2
+ - tests/integration/hints_zone.j2
+ configs:
+ - config
+ - hints
diff --git a/modules/daf/test.integr/kresd_config.j2 b/modules/daf/test.integr/kresd_config.j2
new file mode 100644
index 0000000..0381f77
--- /dev/null
+++ b/modules/daf/test.integr/kresd_config.j2
@@ -0,0 +1,65 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+{% raw %}
+-- make sure DNSSEC is turned off for tests
+trust_anchors.remove('.')
+
+-- 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
+
+modules.load('hints > iterate')
+modules.load('daf')
+
+hints['hints.net.'] = '192.0.2.1'
+
+daf.add('src = 127.0.0.0/8 reroute 192.0.2.1-192.0.2.101')
+
+policy.add(policy.suffix(policy.PASS, {todname('test.')}))
+
+_hint_root_file('hints')
+cache.size = 2*MB
+log_level('debug')
+{% endraw %}
+
+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(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 --git a/modules/daf/test.integr/module_daf.rpl b/modules/daf/test.integr/module_daf.rpl
new file mode 100644
index 0000000..686f04c
--- /dev/null
+++ b/modules/daf/test.integr/module_daf.rpl
@@ -0,0 +1,30 @@
+; SPDX-License-Identifier: GPL-3.0-or-later
+; config options
+; target-fetch-policy: "0 0 0 0 0"
+; module-config: "iterator"
+; name: "."
+ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test DNS Application Firewall
+
+STEP 11 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+hints.net. IN A
+ENTRY_END
+
+; test rewrite rule applies to hints
+STEP 12 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+hints.net. IN A
+SECTION ANSWER
+hints.net. IN A 192.0.2.101
+ENTRY_END
+
+
+SCENARIO_END
diff --git a/modules/detect_time_jump/.packaging/test.config b/modules/detect_time_jump/.packaging/test.config
new file mode 100644
index 0000000..7ed0e60
--- /dev/null
+++ b/modules/detect_time_jump/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('detect_time_jump')
+assert(detect_time_jump)
+quit()
diff --git a/modules/detect_time_jump/README.rst b/modules/detect_time_jump/README.rst
new file mode 100644
index 0000000..066f5a3
--- /dev/null
+++ b/modules/detect_time_jump/README.rst
@@ -0,0 +1,22 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-detect_time_jump:
+
+Detect discontinuous jumps in the system time
+=============================================
+
+This module detect discontinuous jumps in the system time when resolver
+is running. It clears cache when a significant backward time jumps occurs.
+
+Time jumps are usually created by NTP time change or by admin intervention.
+These change can affect cache records as they store timestamp and TTL in real
+time.
+
+If you want to preserve cache during time travel you should disable
+this module by ``modules.unload('detect_time_jump')``.
+
+Due to the way monotonic system time works on typical systems,
+suspend-resume cycles will be perceived as forward time jumps,
+but this direction of shift does not have the risk of using records
+beyond their intended TTL, so forward jumps do not cause erasing the cache.
+
diff --git a/modules/detect_time_jump/detect_time_jump.lua b/modules/detect_time_jump/detect_time_jump.lua
new file mode 100644
index 0000000..6c5b63f
--- /dev/null
+++ b/modules/detect_time_jump/detect_time_jump.lua
@@ -0,0 +1,45 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- Module interface
+local ffi = require('ffi')
+
+local mod = {}
+mod.threshold = 10 * min
+local event_id = nil
+
+-- Get time of last cache clear. Compute difference between realtime
+-- and monotonic time. Compute difference of actual realtime and monotonic
+-- time. In ideal case these differences should be almost same.
+-- If they differ more than mod.threshold value then clear cache.
+local function check_time()
+ local checkpoint = cache.checkpoint()
+ local cache_timeshift = checkpoint.walltime.sec * 1000 - checkpoint.monotime
+ local actual_timeshift = os.time() * 1000 - tonumber(ffi.C.kr_now())
+ local jump_backward = cache_timeshift - actual_timeshift
+ if jump_backward > mod.threshold then
+ log_info(ffi.C.LOG_GRP_DETECTTIMEJUMP, "Detected backwards time jump, clearing cache.\n" ..
+ "But what does that mean? It means your future hasn't been written yet."
+ )
+ cache.clear()
+ elseif -jump_backward > mod.threshold then
+ -- On Linux 4.17+ this shouldn't happen anymore: https://lwn.net/Articles/751482/
+ log_info(ffi.C.LOG_GRP_DETECTTIMEJUMP, "Detected forward time jump. (Suspend-resume, possibly.)")
+ cache.checkpoint(true)
+ end
+end
+
+function mod.init()
+ if event_id then
+ error("Module is already loaded.")
+ else
+ event_id = event.recurrent(1 * min , check_time)
+ end
+end
+
+function mod.deinit()
+ if event_id then
+ event.cancel(event_id)
+ event_id = nil
+ end
+end
+
+return mod
diff --git a/modules/detect_time_skew/.packaging/test.config b/modules/detect_time_skew/.packaging/test.config
new file mode 100644
index 0000000..3a37907
--- /dev/null
+++ b/modules/detect_time_skew/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('detect_time_skew')
+assert(detect_time_skew)
+quit()
diff --git a/modules/detect_time_skew/README.rst b/modules/detect_time_skew/README.rst
new file mode 100644
index 0000000..be66bd0
--- /dev/null
+++ b/modules/detect_time_skew/README.rst
@@ -0,0 +1,23 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-detect_time_skew:
+
+System time skew detector
+=========================
+
+This module compares local system time with inception and expiration time
+bounds in DNSSEC signatures for ``. NS`` records. If the local system time is
+outside of these bounds, it is likely a misconfiguration which will cause
+all DNSSEC validation (and resolution) to fail.
+
+In case of mismatch, a warning message will be logged to help with
+further diagnostics.
+
+.. warning:: Information printed by this module can be forged by a network attacker!
+ System administrator MUST verify values printed by this module and
+ fix local system time using a trusted source.
+
+This module is useful for debugging purposes. It runs only once during resolver
+start does not anything after that. It is enabled by default.
+You may disable the module by appending
+``modules.unload('detect_time_skew')`` to your configuration.
diff --git a/modules/detect_time_skew/detect_time_skew.lua b/modules/detect_time_skew/detect_time_skew.lua
new file mode 100644
index 0000000..6669ce8
--- /dev/null
+++ b/modules/detect_time_skew/detect_time_skew.lua
@@ -0,0 +1,83 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- Module interface
+local ffi = require('ffi')
+
+local mod = {}
+local event_id = nil
+
+-- Resolve callback
+-- Check time validity of RRSIGs in priming query
+-- luacheck: no unused args
+local function check_time_callback(pkt, req)
+ if pkt == nil or pkt:rcode() ~= kres.rcode.NOERROR then
+ log_warn(ffi.C.LOG_GRP_DETECTTIMESKEW, "cannot resolve '.' NS")
+ return nil
+ end
+ local seen_rrsigs = 0
+ local valid_rrsigs = 0
+ local section = pkt:rrsets(kres.section.ANSWER)
+ local now = os.time()
+ local time_diff = 0
+ local inception = 0
+ local expiration = 0
+ for i = 1, #section do
+ local rr = section[i]
+ assert(rr.type)
+ if rr.type == kres.type.RRSIG then
+ for k = 0, rr.rrs.count - 1 do
+ seen_rrsigs = seen_rrsigs + 1
+ local rdata = rr:rdata_pt(k)
+ inception = ffi.C.kr_rrsig_sig_inception(rdata)
+ expiration = ffi.C.kr_rrsig_sig_expiration(rdata)
+ if now > expiration then
+ -- positive value = in the future
+ time_diff = now - expiration
+ elseif now < inception then
+ -- negative value = in the past
+ time_diff = now - inception
+ else
+ valid_rrsigs = valid_rrsigs + 1
+ end
+ end
+ end
+ end
+ if seen_rrsigs == 0 then
+ log_info(ffi.C.LOG_GRP_DETECTTIMESKEW, "No RRSIGs received! "..
+ "You really should configure DNSSEC trust anchor for the root.")
+ elseif valid_rrsigs == 0 then
+ log_warn(ffi.C.LOG_GRP_DETECTTIMESKEW, "Local system time %q seems to be at "..
+ "least %u seconds in the %s. DNSSEC signatures for '.' NS "..
+ "are not valid %s. Please check your system clock!",
+ os.date("%c", now),
+ math.abs(time_diff),
+ time_diff > 0 and "future" or "past",
+ time_diff > 0 and "anymore" or "yet")
+ else
+ log_info(ffi.C.LOG_GRP_DETECTTIMESKEW, "Local system time %q is within "..
+ "RRSIG validity interval <%q,%q>.", os.date("%c", now),
+ os.date("%c", inception), os.date("%c", expiration))
+ end
+end
+
+-- Do uncached priming query and check time validity of RRSIGs.
+local function check_time()
+ resolve(".", kres.type.NS, kres.class.IN, {"DNSSEC_WANT", "DNSSEC_CD", "NO_CACHE"},
+ check_time_callback)
+end
+
+function mod.init()
+ if event_id then
+ error("Module is already loaded.")
+ else
+ event_id = event.after(0 , check_time)
+ end
+end
+
+function mod.deinit()
+ if event_id then
+ event.cancel(event_id)
+ event_id = nil
+ end
+end
+
+return mod
diff --git a/modules/dns64/.packaging/test.config b/modules/dns64/.packaging/test.config
new file mode 100644
index 0000000..5abf524
--- /dev/null
+++ b/modules/dns64/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('dns64')
+assert(dns64)
+quit()
diff --git a/modules/dns64/README.rst b/modules/dns64/README.rst
new file mode 100644
index 0000000..04d2427
--- /dev/null
+++ b/modules/dns64/README.rst
@@ -0,0 +1,62 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-dns64:
+
+DNS64
+=====
+
+The module for :rfc:`6147` DNS64 AAAA-from-A record synthesis, it is used to enable client-server communication between an IPv6-only client and an IPv4-only server. See the well written `introduction`_ in the PowerDNS documentation.
+If no address is passed (i.e. ``nil``), the well-known prefix ``64:ff9b::`` is used.
+
+.. _introduction: https://doc.powerdns.com/md/recursor/dns64
+
+Simple example
+--------------
+
+.. code-block:: lua
+
+ -- Load the module with default settings
+ modules = { 'dns64' }
+ -- Reconfigure later
+ dns64.config({ prefix = '2001:db8::aabb:0:0' })
+
+.. warning:: The module currently won't work well with :func:`policy.STUB`.
+ Also, the IPv6 ``prefix`` passed in configuration is assumed to be ``/96``.
+
+.. tip:: The A record sub-requests will be DNSSEC secured, but the synthetic AAAA records can't be. Make sure the last mile between stub and resolver is secure to avoid spoofing.
+
+
+Advanced options
+----------------
+
+TTL in CNAME generated in the reverse ``ip6.arpa.`` subtree is configurable:
+
+.. code-block:: lua
+
+ dns64.config({ prefix = '2001:db8:77ff::', rev_ttl = 300 })
+
+You can specify a set of IPv6 subnets that are disallowed in answer.
+If they appear, they will be replaced by AAAAs generated from As.
+
+.. code-block:: lua
+
+ dns64.config({
+ prefix = '2001:db8:3::',
+ exclude_subnets = { '2001:db8:888::/48', '::ffff/96' },
+ })
+ -- You could even pass '::/0' to always force using generated AAAAs.
+
+In case you don't want dns64 for all clients,
+you can set ``DNS64_DISABLE`` flag via the :ref:`view module <mod-view>`.
+
+.. code-block:: lua
+
+ modules = { 'dns64', 'view' }
+ -- disable dns64 for all IPv4 source addresses
+ view:addr('0.0.0.0/0', policy.all(policy.FLAGS('DNS64_DISABLE')))
+ -- disable dns64 for all IPv6 source addresses
+ view:addr('::/0', policy.all(policy.FLAGS('DNS64_DISABLE')))
+ -- re-enable dns64 for two IPv6 subnets
+ view:addr('2001:db8:11::/48', policy.all(policy.FLAGS(nil, 'DNS64_DISABLE')))
+ view:addr('2001:db8:93::/48', policy.all(policy.FLAGS(nil, 'DNS64_DISABLE')))
+
diff --git a/modules/dns64/dns64.lua b/modules/dns64/dns64.lua
new file mode 100644
index 0000000..b4fb1ec
--- /dev/null
+++ b/modules/dns64/dns64.lua
@@ -0,0 +1,220 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- Module interface
+local kres = require('kres')
+local ffi = require('ffi')
+local C = ffi.C
+local M = { layer = { } }
+local addr_buf = ffi.new('char[16]')
+
+--[[
+Missing parts of the RFC:
+ > The implementation SHOULD support mapping of separate IPv4 address
+ > ranges to separate IPv6 prefixes for AAAA record synthesis. This
+ > allows handling of special use IPv4 addresses [RFC5735].
+
+ TODO: support different prefix lengths, defaulting to /96 if not specified
+ https://tools.ietf.org/html/rfc6052#section-2.2
+]]
+
+-- Config
+function M.config(conf)
+ if type(conf) ~= 'table' then
+ conf = { prefix = conf }
+ end
+ M.proxy = kres.str2ip(tostring(conf.prefix or '64:ff9b::'))
+ if M.proxy == nil or #M.proxy ~= 16 then
+ error(string.format('[dns64] %q is not a valid IPv6 address', conf.prefix), 2)
+ end
+
+ M.rev_ttl = conf.rev_ttl or 60
+ M.rev_suffix = kres.str2dname(M.proxy
+ :sub(1, 96/8)
+ -- hexdump, reverse, intersperse by dots
+ :gsub('.', function (ch) return string.format('%02x', string.byte(ch)) end)
+ :reverse()
+ :gsub('.', '%1.')
+ .. 'ip6.arpa.'
+ )
+
+ -- RFC 6147.5.1.4
+ M.exclude_subnets = {}
+ if conf.exclude_subnets ~= nil and type(conf.exclude_subnets) ~= 'table' then
+ error('[dns64] .exclude_subnets is not a table')
+ end
+ for _, subnet_cfg in ipairs(conf.exclude_subnets or { '::ffff/96' }) do
+ local subnet = {}
+ subnet.prefix = ffi.new('char[16]')
+ subnet.bitlen = C.kr_straddr_subnet(subnet.prefix, tostring(subnet_cfg))
+ if subnet.bitlen < 0 or not string.find(subnet_cfg, ':', 1, true) then
+ error(string.format('[dns64] failed to parse IPv6 subnet: %q', subnet_cfg))
+ end
+ table.insert(M.exclude_subnets, subnet)
+ end
+end
+
+-- Filter the AAAA records from the last ANSWER, return iff it's NODATA afterwards.
+-- Currently the implementation is lazy and kills it all if any AAAA is excluded.
+local function do_exclude_prefixes(qry)
+ local rrsel = qry.request.answ_selected
+ for i = 0, tonumber(rrsel.len) - 1 do
+ local rr_e = rrsel.at[i] -- struct ranked_rr_array_entry
+ if rr_e.qry_uid ~= qry.uid or rr_e.rr.type ~= kres.type.AAAA or not rr_e.to_wire
+ then goto next_rrset end
+ -- Found answer AAAA RRset
+ for _, subnet in ipairs(M.exclude_subnets) do
+ for j = 0, rr_e.rr:rdcount() - 1 do
+ local rd = rr_e.rr:rdata_pt(j)
+ if rd.len == 16 and C.kr_bitcmp(subnet.prefix, rd.data, subnet.bitlen) == 0 then
+ -- We can't use this RR. TODO: and we're lazy,
+ -- so we kill the whole RRset instead of filtering.
+ rr_e.to_wire = false
+ return true
+ end
+ end
+ end
+ -- We can use the answer -> return false
+ -- We use a nonsensical if to fool the parser; is return adjacent to a label forbidden?
+ if true then return false end
+
+ ::next_rrset::
+ end
+ -- No RRset found, it was probably NODATA.
+ return true
+end
+
+function M.layer.consume(state, req, pkt)
+ if state == kres.FAIL then return state end
+ 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 qry.flags.DNS64_DISABLE
+ or pkt:qclass() ~= kres.class.IN or req.qsource.packet:cd() then
+ return state
+ end
+
+ -- Observe final AAAA NODATA responses to the current SNAME.
+ if pkt:qtype() == kres.type.AAAA and pkt:qname() == qry:name()
+ and qry.flags.RESOLVED and not qry.flags.CNAME and qry.parent == nil
+ and pkt:rcode() == kres.rcode.NOERROR and do_exclude_prefixes(qry) then
+ -- Start a *marked* corresponding A sub-query.
+ local extraFlags = kres.mk_qflags({})
+ extraFlags.DNSSEC_WANT = qry.flags.DNSSEC_WANT
+ extraFlags.AWAIT_CUT = true
+ extraFlags.DNS64_MARK = true
+ req:push(pkt:qname(), kres.type.A, kres.class.IN, extraFlags, qry)
+ return state
+ end
+
+
+ -- Observe answer to the marked sub-query, and convert all A records in ANSWER
+ -- to corresponding AAAA records to be put into the request's answer.
+ if not qry.flags.DNS64_MARK then return state end
+ -- Find rank for the NODATA answer.
+ -- That will result into corresponding AD flag. See RFC 6147 5.5.2.
+ local neg_rank
+ if qry.parent.flags.DNSSEC_WANT and not qry.parent.flags.DNSSEC_INSECURE
+ then neg_rank = ffi.C.KR_RANK_SECURE
+ else neg_rank = ffi.C.KR_RANK_INSECURE
+ end
+ -- Find TTL bound from SOA, according to RFC 6147 5.1.7.4.
+ local max_ttl = 600
+ for i = 1, tonumber(req.auth_selected.len) do
+ local entry = req.auth_selected.at[i - 1]
+ if entry.qry_uid == qry.parent.uid and entry.rr
+ and entry.rr.type == kres.type.SOA
+ and entry.rr.rclass == kres.class.IN then
+ max_ttl = entry.rr:ttl()
+ end
+ end
+ -- Find the As and do the conversion itself.
+ for i = 1, tonumber(req.answ_selected.len) do
+ local orig = req.answ_selected.at[i - 1]
+ if orig.qry_uid == qry.uid and orig.rr.type == kres.type.A then
+ local rank = neg_rank
+ if orig.rank < rank then rank = orig.rank end
+ -- Disable GC, as this object doesn't own owner or RDATA, it's just a reference
+ local ttl = orig.rr:ttl()
+ if ttl > max_ttl then ttl = max_ttl end
+ local rrs = ffi.gc(kres.rrset(nil, kres.type.AAAA, orig.rr.rclass, ttl), nil)
+ rrs._owner = orig.rr._owner
+ for k = 1, orig.rr.rrs.count do
+ local rdata = orig.rr:rdata( k - 1 )
+ ffi.copy(addr_buf, M.proxy, 12)
+ ffi.copy(addr_buf + 12, rdata, 4)
+ ffi.C.knot_rrset_add_rdata(rrs, ffi.string(addr_buf, 16), 16, req.pool)
+ end
+ ffi.C.kr_ranked_rrarray_add(
+ req.answ_selected,
+ rrs,
+ rank,
+ true,
+ qry.uid,
+ req.pool)
+ end
+ end
+ ffi.C.kr_ranked_rrarray_finalize(req.answ_selected, qry.uid, req.pool)
+ req:set_extended_error(kres.extended_error.FORGED, "BHD4: DNS64 synthesis")
+end
+
+local function hexchar2int(char)
+ if char >= string.byte('0') and char <= string.byte('9') then
+ return char - string.byte('0')
+ elseif char >= string.byte('a') and char <= string.byte('f') then
+ return 10 + char - string.byte('a')
+ else
+ return nil
+ end
+end
+
+-- Map the reverse subtree by generating CNAMEs; similarly to the hints module.
+--
+-- RFC 6147.5.3.1.2 says we SHOULD only generate CNAME if it points to data,
+-- but I can't see what's wrong with a CNAME to an NXDOMAIN/NODATA
+-- Reimplementation idea: as-if we had a DNAME in policy/cache?
+function M.layer.produce(_, req, pkt)
+ local qry = req.current_query
+ local sname = qry.sname
+ if ffi.C.knot_dname_in_bailiwick(sname, M.rev_suffix) < 0 or qry.flags.DNS64_DISABLE
+ then return end
+ -- Update packet question if it was minimized.
+ qry.flags.NO_MINIMIZE = true
+ if not ffi.C.knot_dname_is_equal(pkt.wire + 12, sname) or not pkt:has_wire() then
+ if not pkt:recycle() or not pkt:question(sname, qry.sclass, qry.stype)
+ then return end
+ end
+
+ -- Generate a CNAME iff the full address is queried; otherwise leave NODATA.
+ local labels_missing = 16*2 + 2 - ffi.C.knot_dname_labels(sname, nil)
+ if labels_missing == 0 then
+ -- Transforming v6 labels (hex) to v4 ones (decimal) isn't trivial:
+ local labels = sname
+ local v4name = ''
+ for _ = 1, 4 do -- append one IPv4 label at a time into v4name
+ local v4octet = 0
+ for i = 0, 1 do
+ if labels[0] ~= 1 then return end
+ local ch = hexchar2int(labels[1])
+ if not ch then return end
+ v4octet = v4octet + ch * 16^i
+ labels = labels + 2
+ end
+ v4octet = tostring(v4octet)
+ v4name = v4name .. string.char(#v4octet) .. v4octet
+ end
+ v4name = v4name .. '\7in-addr\4arpa\0'
+ if not pkt:put(sname, M.rev_ttl, kres.class.IN, kres.type.CNAME, v4name)
+ then return end
+ end
+
+ -- Simple finishing touches.
+ if labels_missing < 0 then -- and use NXDOMAIN for too long queries
+ pkt:rcode(kres.rcode.NXDOMAIN)
+ else
+ pkt:rcode(kres.rcode.NOERROR)
+ end
+ pkt.parsed = pkt.size
+ pkt:aa(true)
+ pkt:qr(true)
+ qry.flags.CACHED = true
+end
+
+return M
diff --git a/modules/dns64/dns64.test.lua b/modules/dns64/dns64.test.lua
new file mode 100644
index 0000000..45956a4
--- /dev/null
+++ b/modules/dns64/dns64.test.lua
@@ -0,0 +1,53 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local condition = require('cqueues.condition')
+
+-- setup resolver
+modules = { 'hints', 'dns64' }
+hints['dns64.example'] = '192.168.1.1'
+hints.use_nodata(true) -- Respond NODATA to AAAA query
+hints.ttl(60)
+dns64.config('fe80::21b:77ff:0:0')
+
+-- helper to wait for query resolution
+local function wait_resolve(qname, qtype)
+ local waiting, done, cond = false, false, condition.new()
+ local rcode, answers = kres.rcode.SERVFAIL, {}
+ resolve {
+ name = qname,
+ type = qtype,
+ finish = function (answer, _)
+ rcode = answer:rcode()
+ answers = answer:section(kres.section.ANSWER)
+ -- Signal as completed
+ if waiting then
+ cond:signal()
+ end
+ done = true
+ end,
+ }
+ -- Wait if it didn't finish immediately
+ if not done then
+ waiting = true
+ cond:wait()
+ end
+ return rcode, answers
+end
+
+-- test builtin rules
+local function test_builtin_rules()
+ local rcode, answers = wait_resolve('dns64.example', kres.type.AAAA)
+ same(rcode, kres.rcode.NOERROR, 'dns64.example returns NOERROR')
+ same(#answers, 1, 'dns64.example synthesised answer')
+ local expect = {'dns64.example.', '60', 'AAAA', 'fe80::21b:77ff:c0a8:101'}
+ if #answers > 0 then
+ local rr = {kres.rr2str(answers[1]):match('(%S+)%s+(%S+)%s+(%S+)%s+(%S+)')}
+ same(rr, expect, 'dns64.example synthesised correct AAAA record')
+ end
+end
+
+-- plan tests
+local tests = {
+ test_builtin_rules,
+}
+
+return tests
diff --git a/modules/dnstap/.packaging/centos/7/builddeps b/modules/dnstap/.packaging/centos/7/builddeps
new file mode 100644
index 0000000..d3ab354
--- /dev/null
+++ b/modules/dnstap/.packaging/centos/7/builddeps
@@ -0,0 +1,3 @@
+fstrm-devel
+protobuf-c-devel
+protobuf-c-compiler
diff --git a/modules/dnstap/.packaging/centos/7/rundeps b/modules/dnstap/.packaging/centos/7/rundeps
new file mode 100644
index 0000000..06c2792
--- /dev/null
+++ b/modules/dnstap/.packaging/centos/7/rundeps
@@ -0,0 +1,2 @@
+fstrm
+protobuf-c
diff --git a/modules/dnstap/.packaging/centos/8/builddeps b/modules/dnstap/.packaging/centos/8/builddeps
new file mode 100644
index 0000000..d3ab354
--- /dev/null
+++ b/modules/dnstap/.packaging/centos/8/builddeps
@@ -0,0 +1,3 @@
+fstrm-devel
+protobuf-c-devel
+protobuf-c-compiler
diff --git a/modules/dnstap/.packaging/centos/8/rundeps b/modules/dnstap/.packaging/centos/8/rundeps
new file mode 100644
index 0000000..06c2792
--- /dev/null
+++ b/modules/dnstap/.packaging/centos/8/rundeps
@@ -0,0 +1,2 @@
+fstrm
+protobuf-c
diff --git a/modules/dnstap/.packaging/debian/10/builddeps b/modules/dnstap/.packaging/debian/10/builddeps
new file mode 100644
index 0000000..417dc04
--- /dev/null
+++ b/modules/dnstap/.packaging/debian/10/builddeps
@@ -0,0 +1,3 @@
+libfstrm-dev
+libprotobuf-c-dev
+protobuf-c-compiler
diff --git a/modules/dnstap/.packaging/debian/10/rundeps b/modules/dnstap/.packaging/debian/10/rundeps
new file mode 100644
index 0000000..a726e12
--- /dev/null
+++ b/modules/dnstap/.packaging/debian/10/rundeps
@@ -0,0 +1,2 @@
+libfstrm0
+libprotobuf-c1
diff --git a/modules/dnstap/.packaging/debian/9/builddeps b/modules/dnstap/.packaging/debian/9/builddeps
new file mode 100644
index 0000000..417dc04
--- /dev/null
+++ b/modules/dnstap/.packaging/debian/9/builddeps
@@ -0,0 +1,3 @@
+libfstrm-dev
+libprotobuf-c-dev
+protobuf-c-compiler
diff --git a/modules/dnstap/.packaging/debian/9/rundeps b/modules/dnstap/.packaging/debian/9/rundeps
new file mode 100644
index 0000000..a726e12
--- /dev/null
+++ b/modules/dnstap/.packaging/debian/9/rundeps
@@ -0,0 +1,2 @@
+libfstrm0
+libprotobuf-c1
diff --git a/modules/dnstap/.packaging/fedora/31/builddeps b/modules/dnstap/.packaging/fedora/31/builddeps
new file mode 100644
index 0000000..d3ab354
--- /dev/null
+++ b/modules/dnstap/.packaging/fedora/31/builddeps
@@ -0,0 +1,3 @@
+fstrm-devel
+protobuf-c-devel
+protobuf-c-compiler
diff --git a/modules/dnstap/.packaging/fedora/31/rundeps b/modules/dnstap/.packaging/fedora/31/rundeps
new file mode 100644
index 0000000..06c2792
--- /dev/null
+++ b/modules/dnstap/.packaging/fedora/31/rundeps
@@ -0,0 +1,2 @@
+fstrm
+protobuf-c
diff --git a/modules/dnstap/.packaging/fedora/32/builddeps b/modules/dnstap/.packaging/fedora/32/builddeps
new file mode 100644
index 0000000..d3ab354
--- /dev/null
+++ b/modules/dnstap/.packaging/fedora/32/builddeps
@@ -0,0 +1,3 @@
+fstrm-devel
+protobuf-c-devel
+protobuf-c-compiler
diff --git a/modules/dnstap/.packaging/fedora/32/rundeps b/modules/dnstap/.packaging/fedora/32/rundeps
new file mode 100644
index 0000000..06c2792
--- /dev/null
+++ b/modules/dnstap/.packaging/fedora/32/rundeps
@@ -0,0 +1,2 @@
+fstrm
+protobuf-c
diff --git a/modules/dnstap/.packaging/leap/15.2/builddeps b/modules/dnstap/.packaging/leap/15.2/builddeps
new file mode 100644
index 0000000..30f8d9e
--- /dev/null
+++ b/modules/dnstap/.packaging/leap/15.2/builddeps
@@ -0,0 +1,3 @@
+fstrm-devel
+libprotobuf-c-devel
+protobuf-c
diff --git a/modules/dnstap/.packaging/leap/15.2/rundeps b/modules/dnstap/.packaging/leap/15.2/rundeps
new file mode 100644
index 0000000..06c2792
--- /dev/null
+++ b/modules/dnstap/.packaging/leap/15.2/rundeps
@@ -0,0 +1,2 @@
+fstrm
+protobuf-c
diff --git a/modules/dnstap/.packaging/test.config b/modules/dnstap/.packaging/test.config
new file mode 100644
index 0000000..5966860
--- /dev/null
+++ b/modules/dnstap/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('dnstap')
+assert(dnstap)
+quit()
diff --git a/modules/dnstap/.packaging/ubuntu/16.04/builddeps b/modules/dnstap/.packaging/ubuntu/16.04/builddeps
new file mode 100644
index 0000000..417dc04
--- /dev/null
+++ b/modules/dnstap/.packaging/ubuntu/16.04/builddeps
@@ -0,0 +1,3 @@
+libfstrm-dev
+libprotobuf-c-dev
+protobuf-c-compiler
diff --git a/modules/dnstap/.packaging/ubuntu/16.04/rundeps b/modules/dnstap/.packaging/ubuntu/16.04/rundeps
new file mode 100644
index 0000000..a726e12
--- /dev/null
+++ b/modules/dnstap/.packaging/ubuntu/16.04/rundeps
@@ -0,0 +1,2 @@
+libfstrm0
+libprotobuf-c1
diff --git a/modules/dnstap/.packaging/ubuntu/18.04/builddeps b/modules/dnstap/.packaging/ubuntu/18.04/builddeps
new file mode 100644
index 0000000..417dc04
--- /dev/null
+++ b/modules/dnstap/.packaging/ubuntu/18.04/builddeps
@@ -0,0 +1,3 @@
+libfstrm-dev
+libprotobuf-c-dev
+protobuf-c-compiler
diff --git a/modules/dnstap/.packaging/ubuntu/18.04/rundeps b/modules/dnstap/.packaging/ubuntu/18.04/rundeps
new file mode 100644
index 0000000..a726e12
--- /dev/null
+++ b/modules/dnstap/.packaging/ubuntu/18.04/rundeps
@@ -0,0 +1,2 @@
+libfstrm0
+libprotobuf-c1
diff --git a/modules/dnstap/.packaging/ubuntu/20.04/builddeps b/modules/dnstap/.packaging/ubuntu/20.04/builddeps
new file mode 100644
index 0000000..417dc04
--- /dev/null
+++ b/modules/dnstap/.packaging/ubuntu/20.04/builddeps
@@ -0,0 +1,3 @@
+libfstrm-dev
+libprotobuf-c-dev
+protobuf-c-compiler
diff --git a/modules/dnstap/.packaging/ubuntu/20.04/rundeps b/modules/dnstap/.packaging/ubuntu/20.04/rundeps
new file mode 100644
index 0000000..a726e12
--- /dev/null
+++ b/modules/dnstap/.packaging/ubuntu/20.04/rundeps
@@ -0,0 +1,2 @@
+libfstrm0
+libprotobuf-c1
diff --git a/modules/dnstap/README.rst b/modules/dnstap/README.rst
new file mode 100644
index 0000000..456d218
--- /dev/null
+++ b/modules/dnstap/README.rst
@@ -0,0 +1,42 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-dnstap:
+
+Dnstap (traffic collection)
+===========================
+
+The ``dnstap`` module supports logging DNS requests and responses to a unix
+socket in `dnstap format <https://dnstap.info>`_ using fstrm framing library.
+This logging is useful if you need effectively log all DNS traffic.
+
+The unix socket and the socket reader must be present before starting resolver instances.
+Also it needs appropriate filesystem permissions;
+the typical user and group of the daemon are called ``knot-resolver``.
+
+Tunables:
+
+* ``socket_path``: the unix socket file where dnstap messages will be sent
+* ``identity``: identity string as typically returned by an "NSID" (RFC 5001) query, empty by default
+* ``version``: version string of the resolver, defaulting to "Knot Resolver major.minor.patch"
+* ``client.log_queries``: if ``true`` queries from downstream in wire format will be logged
+* ``client.log_responses``: if ``true`` responses to downstream in wire format will be logged
+
+.. Very non-standard and it seems unlikely that others want to collect the RTT.
+.. * ``client.log_tcp_rtt``: if ``true`` and on Linux,
+ add "extra" field with "rtt=12345\n",
+ signifying kernel's current estimate of RTT micro-seconds for the non-UDP connection
+ (alongside every arrived DNS message).
+
+.. code-block:: lua
+
+ modules = {
+ dnstap = {
+ socket_path = "/tmp/dnstap.sock",
+ identity = nsid.name() or "",
+ version = "My Custom Knot Resolver " .. package_version(),
+ client = {
+ log_queries = true,
+ log_responses = true,
+ },
+ }
+ }
diff --git a/modules/dnstap/dnstap.c b/modules/dnstap/dnstap.c
new file mode 100644
index 0000000..7572667
--- /dev/null
+++ b/modules/dnstap/dnstap.c
@@ -0,0 +1,524 @@
+/*
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * @file dnstap.c
+ * @brief dnstap based query logging support
+ *
+ */
+
+#include "lib/module.h"
+#include "modules/dnstap/dnstap.pb-c.h"
+
+#include "contrib/cleanup.h"
+#include "daemon/session.h"
+#include "daemon/worker.h"
+#include "lib/layer.h"
+#include "lib/resolve.h"
+
+#include <ccan/json/json.h>
+#include <fstrm.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <uv.h>
+
+#define DEBUG_MSG(fmt, ...) kr_log_debug(DNSTAP, fmt, ##__VA_ARGS__);
+#define ERROR_MSG(fmt, ...) kr_log_error(DNSTAP, fmt, ##__VA_ARGS__);
+#define CFG_SOCK_PATH "socket_path"
+#define CFG_IDENTITY_STRING "identity"
+#define CFG_VERSION_STRING "version"
+#define CFG_LOG_CLIENT_PKT "client"
+#define CFG_LOG_QR_PKT "log_queries"
+#define CFG_LOG_RESP_PKT "log_responses"
+#define CFG_LOG_TCP_RTT "log_tcp_rtt"
+#define DEFAULT_SOCK_PATH "/tmp/dnstap.sock"
+#define DNSTAP_CONTENT_TYPE "protobuf:dnstap.Dnstap"
+#define DNSTAP_INITIAL_BUF_SIZE 256
+
+#define auto_destroy_uopts __attribute__((cleanup(fstrm_unix_writer_options_destroy)))
+#define auto_destroy_wopts __attribute__((cleanup(fstrm_writer_options_destroy)))
+
+/*
+ * Internal processing phase
+ * Distinguishes whether query or response should be processed
+ */
+enum dnstap_log_phase {
+ CLIENT_QUERY_PHASE = 0,
+ CLIENT_RESPONSE_PHASE,
+};
+
+/* Internal data structure */
+struct dnstap_data {
+ char *identity;
+ size_t identity_len;
+ char *version;
+ size_t version_len;
+ bool log_qr_pkt;
+ bool log_resp_pkt;
+ bool log_tcp_rtt;
+ struct fstrm_iothr *iothread;
+ struct fstrm_iothr_queue *ioq;
+};
+
+/*
+ * dt_pack packs the dnstap message for transport
+ * 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)
+{
+ ProtobufCBufferSimple sbuf = { { NULL } };
+
+ sbuf.base.append = protobuf_c_buffer_simple_append;
+ sbuf.len = 0;
+ sbuf.alloced = DNSTAP_INITIAL_BUF_SIZE;
+ sbuf.data = malloc(sbuf.alloced);
+ if (sbuf.data == NULL) {
+ return NULL;
+ }
+ sbuf.must_free_data = true;
+
+ *sz = dnstap__dnstap__pack_to_buffer(d, (ProtobufCBuffer *) &sbuf);
+ *buf = sbuf.data;
+ return *buf;
+}
+
+/* set_address fills in address detail in dnstap_message
+ * 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,
+ protobuf_c_boolean *has_addr,
+ uint32_t *port,
+ protobuf_c_boolean *has_port) {
+ const char *saddr = kr_inaddr(sockaddr);
+ if (saddr == NULL) {
+ *has_addr = false;
+ *has_port = false;
+ return;
+ }
+
+ addr->data = (uint8_t *)(saddr);
+ addr->len = kr_inaddr_len(sockaddr);
+ *has_addr = true;
+ *port = kr_inaddr_port(sockaddr);
+ *has_port = true;
+}
+
+#ifndef HAS_TCP_INFO
+ /* TCP RTT: not portable; not sure where else it might work. */
+ #define HAS_TCP_INFO __linux__
+#endif
+#if HAS_TCP_INFO
+/** Fill a tcp_info or return kr_error(). */
+static int get_tcp_info(const struct kr_request *req, struct tcp_info *info)
+{
+ if(kr_fails_assert(req && info))
+ return kr_error(EINVAL);
+ if (!req->qsource.dst_addr || !req->qsource.flags.tcp) /* not TCP-based */
+ return -abs(ENOENT);
+ /* First obtain the file-descriptor. */
+ uv_handle_t *h = session_get_handle(worker_request_get_source_session(req));
+ uv_os_fd_t fd;
+ int ret = uv_fileno(h, &fd);
+ if (ret)
+ return kr_error(ret);
+
+ socklen_t tcp_info_length = sizeof(*info);
+ if (getsockopt(fd, SOL_TCP, TCP_INFO, info, &tcp_info_length))
+ return kr_error(errno);
+ return kr_ok();
+}
+#endif
+
+/* dnstap_log prepares dnstap message and sends it to fstrm
+ *
+ * Return codes are kr_error(E*) and unused for now.
+ */
+static int dnstap_log(kr_layer_t *ctx, enum dnstap_log_phase phase) {
+ const struct kr_request *req = ctx->req;
+ const struct kr_module *module = ctx->api->data;
+ const struct kr_rplan *rplan = &req->rplan;
+ const struct dnstap_data *dnstap_dt = module->data;
+
+ if (!req->qsource.addr) {
+ return kr_ok();
+ }
+
+ /* check if we have a valid iothread */
+ if (!dnstap_dt->iothread || !dnstap_dt->ioq) {
+ DEBUG_MSG("dnstap_dt->iothread or dnstap_dt->ioq is NULL\n");
+ return kr_error(EFAULT);
+ }
+
+ /* Create dnstap message */
+ Dnstap__Message m;
+ Dnstap__Dnstap dnstap = DNSTAP__DNSTAP__INIT;
+ dnstap.type = DNSTAP__DNSTAP__TYPE__MESSAGE;
+ dnstap.message = &m;
+
+ memset(&m, 0, sizeof(m));
+
+ m.base.descriptor = &dnstap__message__descriptor;
+
+ if (req->qsource.addr) {
+ set_address(req->qsource.addr,
+ &m.query_address,
+ &m.has_query_address,
+ &m.query_port,
+ &m.has_query_port);
+ }
+
+ if (req->qsource.dst_addr) {
+ if (req->qsource.flags.http) {
+ m.socket_protocol = DNSTAP__SOCKET_PROTOCOL__DOH;
+ } else if (req->qsource.flags.tls) {
+ m.socket_protocol = DNSTAP__SOCKET_PROTOCOL__DOT;
+ } else if (req->qsource.flags.tcp) {
+ m.socket_protocol = DNSTAP__SOCKET_PROTOCOL__TCP;
+ } else {
+ m.socket_protocol = DNSTAP__SOCKET_PROTOCOL__UDP;
+ }
+ m.has_socket_protocol = true;
+
+ set_address(req->qsource.dst_addr,
+ &m.response_address,
+ &m.has_response_address,
+ &m.response_port,
+ &m.has_response_port);
+ switch (req->qsource.dst_addr->sa_family) {
+ case AF_INET:
+ m.socket_family = DNSTAP__SOCKET_FAMILY__INET;
+ m.has_socket_family = true;
+ break;
+ case AF_INET6:
+ m.socket_family = DNSTAP__SOCKET_FAMILY__INET6;
+ m.has_socket_family = true;
+ break;
+ }
+ }
+
+ char dnstap_extra_buf[24];
+ if (phase == CLIENT_QUERY_PHASE) {
+ m.type = DNSTAP__MESSAGE__TYPE__CLIENT_QUERY;
+
+ if (dnstap_dt->log_qr_pkt) {
+ const knot_pkt_t *qpkt = req->qsource.packet;
+ m.has_query_message = qpkt != NULL;
+ if (qpkt != NULL) {
+ m.query_message.len = qpkt->size;
+ m.query_message.data = qpkt->wire;
+ }
+ }
+
+ /* set query time to the timestamp of the first kr_query */
+ if (rplan->initial) {
+ struct kr_query *first = rplan->initial;
+
+ m.query_time_sec = first->timestamp.tv_sec;
+ m.has_query_time_sec = true;
+ m.query_time_nsec = first->timestamp.tv_usec * 1000;
+ m.has_query_time_nsec = true;
+ }
+#if HAS_TCP_INFO
+ struct tcp_info ti = { 0 };
+ if (dnstap_dt->log_tcp_rtt && get_tcp_info(req, &ti) == kr_ok()) {
+ int len = snprintf(dnstap_extra_buf, sizeof(dnstap_extra_buf),
+ "rtt=%u\n", (unsigned)ti.tcpi_rtt);
+ if (len < sizeof(dnstap_extra_buf)) {
+ dnstap.extra.data = (uint8_t *)dnstap_extra_buf;
+ dnstap.extra.len = len;
+ dnstap.has_extra = true;
+ }
+ }
+#else
+ (void)dnstap_extra_buf;
+#endif
+ } else if (phase == CLIENT_RESPONSE_PHASE) {
+ m.type = DNSTAP__MESSAGE__TYPE__CLIENT_RESPONSE;
+
+ /* current time */
+ struct timeval now;
+ gettimeofday(&now, NULL);
+
+ if (dnstap_dt->log_resp_pkt) {
+ const knot_pkt_t *rpkt = req->answer;
+ m.has_response_message = rpkt != NULL;
+ if (rpkt != NULL) {
+ m.response_message.len = rpkt->size;
+ m.response_message.data = rpkt->wire;
+ }
+ }
+
+ /* Set response time to now */
+ m.response_time_sec = now.tv_sec;
+ m.has_response_time_sec = true;
+ m.response_time_nsec = now.tv_usec * 1000;
+ m.has_response_time_nsec = true;
+ }
+
+ if (dnstap_dt->identity) {
+ dnstap.identity.data = (uint8_t*)dnstap_dt->identity;
+ dnstap.identity.len = dnstap_dt->identity_len;
+ dnstap.has_identity = true;
+ }
+
+ if (dnstap_dt->version) {
+ dnstap.version.data = (uint8_t*)dnstap_dt->version;
+ dnstap.version.len = dnstap_dt->version_len;
+ dnstap.has_version = true;
+ }
+
+ /* Pack the message */
+ uint8_t *frame = NULL;
+ size_t size = 0;
+ dt_pack(&dnstap, &frame, &size);
+ if (!frame) {
+ return kr_error(ENOMEM);
+ }
+
+ /* Submit a request to send message to fstrm_iothr*/
+ fstrm_res res = fstrm_iothr_submit(dnstap_dt->iothread, dnstap_dt->ioq, frame, size,
+ fstrm_free_wrapper, NULL);
+ if (res != fstrm_res_success) {
+ DEBUG_MSG("Error submitting dnstap message to iothr\n");
+ free(frame);
+ return kr_error(EBUSY);
+ }
+
+ return kr_ok();
+}
+
+/* dnstap_log_query prepares dnstap CLIENT_QUERY message and sends it to fstrm */
+static int dnstap_log_query(kr_layer_t *ctx) {
+ dnstap_log(ctx, CLIENT_QUERY_PHASE);
+ return ctx->state;
+}
+
+/* dnstap_log_response prepares dnstap CLIENT_RESPONSE message and sends it to fstrm */
+static int dnstap_log_response(kr_layer_t *ctx) {
+ dnstap_log(ctx, CLIENT_RESPONSE_PHASE);
+ return ctx->state;
+}
+
+KR_EXPORT
+int dnstap_init(struct kr_module *module) {
+ static kr_layer_api_t layer = {
+ .begin = &dnstap_log_query,
+ .finish = &dnstap_log_response,
+ };
+ /* Store module reference */
+ layer.data = module;
+ module->layer = &layer;
+
+ /* allocated memory for internal data */
+ struct dnstap_data *data = calloc(1, sizeof(*data));
+ if (!data) {
+ return kr_error(ENOMEM);
+ }
+
+ /* save pointer to internal struct in module for future reference */
+ module->data = data;
+ return kr_ok();
+}
+
+/** Clear, i.e. get to state as after the first dnstap_init(). */
+static void dnstap_clear(struct kr_module *module) {
+ struct dnstap_data *data = module->data;
+ if (data) {
+ free(data->identity);
+ free(data->version);
+
+ fstrm_iothr_destroy(&data->iothread);
+ DEBUG_MSG("fstrm iothread destroyed\n");
+ }
+}
+
+KR_EXPORT
+int dnstap_deinit(struct kr_module *module) {
+ dnstap_clear(module);
+ free(module->data);
+ return kr_ok();
+}
+
+/* dnstap_unix_writer returns a unix fstream writer
+ * 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) {
+
+ auto_destroy_uopts struct fstrm_unix_writer_options *opt = fstrm_unix_writer_options_init();
+ if (!opt) {
+ return NULL;
+ }
+ fstrm_unix_writer_options_set_socket_path(opt, path);
+
+ auto_destroy_wopts struct fstrm_writer_options *wopt = fstrm_writer_options_init();
+ if (!wopt) {
+ fstrm_unix_writer_options_destroy(&opt);
+ return NULL;
+ }
+ fstrm_writer_options_add_content_type(wopt, DNSTAP_CONTENT_TYPE,
+ strlen(DNSTAP_CONTENT_TYPE));
+
+ struct fstrm_writer *writer = fstrm_unix_writer_init(opt, wopt);
+ fstrm_unix_writer_options_destroy(&opt);
+ fstrm_writer_options_destroy(&wopt);
+ if (!writer) {
+ return NULL;
+ }
+
+ fstrm_res res = fstrm_writer_open(writer);
+ if (res != fstrm_res_success) {
+ DEBUG_MSG("fstrm_writer_open returned %d\n", res);
+ fstrm_writer_destroy(&writer);
+ return NULL;
+ }
+
+ return writer;
+}
+
+/* find_string
+ * create a new string from json
+ * *var is set to pointer of new string
+ * node must of type JSON_STRING
+ * new string can be at most len bytes
+ */
+static int find_string(const JsonNode *node, char **val, size_t len) {
+ if (!node || !node->key)
+ return kr_error(EINVAL);
+ if (kr_fails_assert(node->tag == JSON_STRING))
+ return kr_error(EINVAL);
+ *val = strndup(node->string_, len);
+ if (kr_fails_assert(*val != NULL))
+ return kr_error(errno);
+ return kr_ok();
+}
+
+/* find_bool returns bool from json */
+static bool find_bool(const JsonNode *node) {
+ if (!node || !node->key)
+ return false;
+ if (kr_fails_assert(node->tag == JSON_BOOL))
+ return false;
+ return node->bool_;
+}
+
+/* parse config */
+KR_EXPORT
+int dnstap_config(struct kr_module *module, const char *conf) {
+ dnstap_clear(module);
+ if (!conf) return kr_ok(); /* loaded module without configuring */
+ struct dnstap_data *data = module->data;
+ auto_free char *sock_path = NULL;
+
+ /* Empty conf passed, set default */
+ if (strlen(conf) < 1) {
+ sock_path = strdup(DEFAULT_SOCK_PATH);
+ } else {
+
+ JsonNode *root_node = json_decode(conf);
+ if (!root_node) {
+ ERROR_MSG("error parsing json\n");
+ return kr_error(EINVAL);
+ }
+
+ JsonNode *node;
+ /* dnstapPath key */
+ node = json_find_member(root_node, CFG_SOCK_PATH);
+ if (!node || find_string(node, &sock_path, PATH_MAX) != kr_ok()) {
+ sock_path = strdup(DEFAULT_SOCK_PATH);
+ }
+
+ /* identity string key */
+ node = json_find_member(root_node, CFG_IDENTITY_STRING);
+ if (!node || find_string(node, &data->identity, KR_EDNS_PAYLOAD) != kr_ok()) {
+ data->identity = NULL;
+ data->identity_len = 0;
+ } else {
+ data->identity_len = strlen(data->identity);
+ }
+
+ /* version string key */
+ node = json_find_member(root_node, CFG_VERSION_STRING);
+ if (!node || find_string(node, &data->version, KR_EDNS_PAYLOAD) != kr_ok()) {
+ data->version = strdup("Knot Resolver " PACKAGE_VERSION);
+ if (data->version) {
+ data->version_len = strlen(data->version);
+ }
+ } else {
+ data->version_len = strlen(data->version);
+ }
+
+ node = json_find_member(root_node, CFG_LOG_CLIENT_PKT);
+ if (node) {
+ JsonNode *subnode;
+ /* logRespPkt key */
+ subnode = json_find_member(node, CFG_LOG_RESP_PKT);
+ if (subnode) {
+ data->log_resp_pkt = find_bool(subnode);
+ } else {
+ data->log_resp_pkt = false;
+ }
+
+ /* logQrPkt key */
+ subnode = json_find_member(node, CFG_LOG_QR_PKT);
+ if (subnode) {
+ data->log_qr_pkt = find_bool(subnode);
+ } else {
+ data->log_qr_pkt = false;
+ }
+
+ subnode = json_find_member(node, CFG_LOG_TCP_RTT);
+ if (subnode) {
+ data->log_tcp_rtt = find_bool(subnode);
+ } else {
+ data->log_tcp_rtt = false;
+ }
+ } else {
+ data->log_qr_pkt = false;
+ data->log_resp_pkt = false;
+ data->log_tcp_rtt = false;
+ }
+
+ /* clean up json, we don't need it no more */
+ json_delete(root_node);
+ }
+
+ DEBUG_MSG("opening sock file %s\n",sock_path);
+ struct fstrm_writer *writer = dnstap_unix_writer(sock_path);
+ if (!writer) {
+ ERROR_MSG("failed to open socket %s\n"
+ "Please ensure that it exists beforehand and has appropriate access permissions.\n",
+ sock_path);
+ return kr_error(EINVAL);
+ }
+
+ struct fstrm_iothr_options *opt = fstrm_iothr_options_init();
+ if (!opt) {
+ ERROR_MSG("can't init fstrm options\n");
+ fstrm_writer_destroy(&writer);
+ return kr_error(EINVAL);
+ }
+
+ /* Create the I/O thread. */
+ data->iothread = fstrm_iothr_init(opt, &writer);
+ fstrm_iothr_options_destroy(&opt);
+ if (!data->iothread) {
+ ERROR_MSG("can't init fstrm_iothr\n");
+ fstrm_writer_destroy(&writer);
+ return kr_error(ENOMEM);
+ }
+
+ /* Get fstrm thread handle
+ * We only have one input queue, hence idx=0
+ */
+ data->ioq = fstrm_iothr_get_input_queue_idx(data->iothread, 0);
+ if (!data->ioq) {
+ fstrm_iothr_destroy(&data->iothread);
+ ERROR_MSG("can't get fstrm queue\n");
+ return kr_error(EBUSY);
+ }
+
+ return kr_ok();
+}
+
+KR_MODULE_EXPORT(dnstap)
+
diff --git a/modules/dnstap/dnstap.proto b/modules/dnstap/dnstap.proto
new file mode 100644
index 0000000..f2b7273
--- /dev/null
+++ b/modules/dnstap/dnstap.proto
@@ -0,0 +1,273 @@
+// dnstap: flexible, structured event replication format for DNS software
+//
+// This file contains the protobuf schemas for the "dnstap" structured event
+// replication format for DNS software.
+
+// Written in 2013-2014 by Farsight Security, Inc.
+//
+// SPDX-License-Identifier: CC0-1.0
+
+syntax = "proto2";
+package dnstap;
+
+// "Dnstap": this is the top-level dnstap type, which is a "union" type that
+// contains other kinds of dnstap payloads, although currently only one type
+// of dnstap payload is defined.
+// See: https://developers.google.com/protocol-buffers/docs/techniques#union
+message Dnstap {
+ // DNS server identity.
+ // If enabled, this is the identity string of the DNS server which generated
+ // this message. Typically this would be the same string as returned by an
+ // "NSID" (RFC 5001) query.
+ optional bytes identity = 1;
+
+ // DNS server version.
+ // If enabled, this is the version string of the DNS server which generated
+ // this message. Typically this would be the same string as returned by a
+ // "version.bind" query.
+ optional bytes version = 2;
+
+ // Extra data for this payload.
+ // This field can be used for adding an arbitrary byte-string annotation to
+ // the payload. No encoding or interpretation is applied or enforced.
+ optional bytes extra = 3;
+
+ // Identifies which field below is filled in.
+ enum Type {
+ MESSAGE = 1;
+ }
+ required Type type = 15;
+
+ // One of the following will be filled in.
+ optional Message message = 14;
+}
+
+// SocketFamily: the network protocol family of a socket. This specifies how
+// to interpret "network address" fields.
+enum SocketFamily {
+ INET = 1; // IPv4 (RFC 791)
+ INET6 = 2; // IPv6 (RFC 2460)
+}
+
+// SocketProtocol: the protocol used to transport a DNS message.
+enum SocketProtocol {
+ UDP = 1; // DNS over UDP transport (RFC 1035 section 4.2.1)
+ TCP = 2; // DNS over TCP transport (RFC 1035 section 4.2.2)
+ DOT = 3; // DNS over TLS (RFC 7858)
+ DOH = 4; // DNS over HTTPS (RFC 8484)
+}
+
+// Message: a wire-format (RFC 1035 section 4) DNS message and associated
+// metadata. Applications generating "Message" payloads should follow
+// certain requirements based on the MessageType, see below.
+message Message {
+
+ // There are eight types of "Message" defined that correspond to the
+ // four arrows in the following diagram, slightly modified from RFC 1035
+ // section 2:
+
+ // +---------+ +----------+ +--------+
+ // | | query | | query | |
+ // | Stub |-SQ--------CQ->| Recursive|-RQ----AQ->| Auth. |
+ // | Resolver| | Server | | Name |
+ // | |<-SR--------CR-| |<-RR----AR-| Server |
+ // +---------+ response | | response | |
+ // +----------+ +--------+
+
+ // Each arrow has two Type values each, one for each "end" of each arrow,
+ // because these are considered to be distinct events. Each end of each
+ // arrow on the diagram above has been marked with a two-letter Type
+ // mnemonic. Clockwise from upper left, these mnemonic values are:
+ //
+ // SQ: STUB_QUERY
+ // CQ: CLIENT_QUERY
+ // RQ: RESOLVER_QUERY
+ // AQ: AUTH_QUERY
+ // AR: AUTH_RESPONSE
+ // RR: RESOLVER_RESPONSE
+ // CR: CLIENT_RESPONSE
+ // SR: STUB_RESPONSE
+
+ // Two additional types of "Message" have been defined for the
+ // "forwarding" case where an upstream DNS server is responsible for
+ // further recursion. These are not shown on the diagram above, but have
+ // the following mnemonic values:
+
+ // FQ: FORWARDER_QUERY
+ // FR: FORWARDER_RESPONSE
+
+ // The "Message" Type values are defined below.
+
+ enum Type {
+ // AUTH_QUERY is a DNS query message received from a resolver by an
+ // authoritative name server, from the perspective of the authoritative
+ // name server.
+ AUTH_QUERY = 1;
+
+ // AUTH_RESPONSE is a DNS response message sent from an authoritative
+ // name server to a resolver, from the perspective of the authoritative
+ // name server.
+ AUTH_RESPONSE = 2;
+
+ // RESOLVER_QUERY is a DNS query message sent from a resolver to an
+ // authoritative name server, from the perspective of the resolver.
+ // Resolvers typically clear the RD (recursion desired) bit when
+ // sending queries.
+ RESOLVER_QUERY = 3;
+
+ // RESOLVER_RESPONSE is a DNS response message received from an
+ // authoritative name server by a resolver, from the perspective of
+ // the resolver.
+ RESOLVER_RESPONSE = 4;
+
+ // CLIENT_QUERY is a DNS query message sent from a client to a DNS
+ // server which is expected to perform further recursion, from the
+ // perspective of the DNS server. The client may be a stub resolver or
+ // forwarder or some other type of software which typically sets the RD
+ // (recursion desired) bit when querying the DNS server. The DNS server
+ // may be a simple forwarding proxy or it may be a full recursive
+ // resolver.
+ CLIENT_QUERY = 5;
+
+ // CLIENT_RESPONSE is a DNS response message sent from a DNS server to
+ // a client, from the perspective of the DNS server. The DNS server
+ // typically sets the RA (recursion available) bit when responding.
+ CLIENT_RESPONSE = 6;
+
+ // FORWARDER_QUERY is a DNS query message sent from a downstream DNS
+ // server to an upstream DNS server which is expected to perform
+ // further recursion, from the perspective of the downstream DNS
+ // server.
+ FORWARDER_QUERY = 7;
+
+ // FORWARDER_RESPONSE is a DNS response message sent from an upstream
+ // DNS server performing recursion to a downstream DNS server, from the
+ // perspective of the downstream DNS server.
+ FORWARDER_RESPONSE = 8;
+
+ // STUB_QUERY is a DNS query message sent from a stub resolver to a DNS
+ // server, from the perspective of the stub resolver.
+ STUB_QUERY = 9;
+
+ // STUB_RESPONSE is a DNS response message sent from a DNS server to a
+ // stub resolver, from the perspective of the stub resolver.
+ STUB_RESPONSE = 10;
+
+ // TOOL_QUERY is a DNS query message sent from a DNS software tool to a
+ // DNS server, from the perspective of the tool.
+ TOOL_QUERY = 11;
+
+ // TOOL_RESPONSE is a DNS response message received by a DNS software
+ // tool from a DNS server, from the perspective of the tool.
+ TOOL_RESPONSE = 12;
+
+ // UPDATE_QUERY is a DNS update query message received from a resolver
+ // by an authoritative name server, from the perspective of the
+ // authoritative name server.
+ UPDATE_QUERY = 13;
+
+ // UPDATE_RESPONSE is a DNS update response message sent from an
+ // authoritative name server to a resolver, from the perspective of the
+ // authoritative name server.
+ UPDATE_RESPONSE = 14;
+ }
+
+ // One of the Type values described above.
+ required Type type = 1;
+
+ // One of the SocketFamily values described above.
+ optional SocketFamily socket_family = 2;
+
+ // One of the SocketProtocol values described above.
+ optional SocketProtocol socket_protocol = 3;
+
+ // The network address of the message initiator.
+ // For SocketFamily INET, this field is 4 octets (IPv4 address).
+ // For SocketFamily INET6, this field is 16 octets (IPv6 address).
+ optional bytes query_address = 4;
+
+ // The network address of the message responder.
+ // For SocketFamily INET, this field is 4 octets (IPv4 address).
+ // For SocketFamily INET6, this field is 16 octets (IPv6 address).
+ optional bytes response_address = 5;
+
+ // The transport port of the message initiator.
+ // This is a 16-bit UDP or TCP port number, depending on SocketProtocol.
+ optional uint32 query_port = 6;
+
+ // The transport port of the message responder.
+ // This is a 16-bit UDP or TCP port number, depending on SocketProtocol.
+ optional uint32 response_port = 7;
+
+ // The time at which the DNS query message was sent or received, depending
+ // on whether this is an AUTH_QUERY, RESOLVER_QUERY, or CLIENT_QUERY.
+ // This is the number of seconds since the UNIX epoch.
+ optional uint64 query_time_sec = 8;
+
+ // The time at which the DNS query message was sent or received.
+ // This is the seconds fraction, expressed as a count of nanoseconds.
+ optional fixed32 query_time_nsec = 9;
+
+ // The initiator's original wire-format DNS query message, verbatim.
+ optional bytes query_message = 10;
+
+ // The "zone" or "bailiwick" pertaining to the DNS query message.
+ // This is a wire-format DNS domain name.
+ optional bytes query_zone = 11;
+
+ // The time at which the DNS response message was sent or received,
+ // depending on whether this is an AUTH_RESPONSE, RESOLVER_RESPONSE, or
+ // CLIENT_RESPONSE.
+ // This is the number of seconds since the UNIX epoch.
+ optional uint64 response_time_sec = 12;
+
+ // The time at which the DNS response message was sent or received.
+ // This is the seconds fraction, expressed as a count of nanoseconds.
+ optional fixed32 response_time_nsec = 13;
+
+ // The responder's original wire-format DNS response message, verbatim.
+ optional bytes response_message = 14;
+}
+
+// All fields except for 'type' in the Message schema are optional.
+// It is recommended that at least the following fields be filled in for
+// particular types of Messages.
+
+// AUTH_QUERY:
+// socket_family, socket_protocol
+// query_address, query_port
+// query_message
+// query_time_sec, query_time_nsec
+
+// AUTH_RESPONSE:
+// socket_family, socket_protocol
+// query_address, query_port
+// query_time_sec, query_time_nsec
+// response_message
+// response_time_sec, response_time_nsec
+
+// RESOLVER_QUERY:
+// socket_family, socket_protocol
+// query_message
+// query_time_sec, query_time_nsec
+// query_zone
+// response_address, response_port
+
+// RESOLVER_RESPONSE:
+// socket_family, socket_protocol
+// query_time_sec, query_time_nsec
+// query_zone
+// response_address, response_port
+// response_message
+// response_time_sec, response_time_nsec
+
+// CLIENT_QUERY:
+// socket_family, socket_protocol
+// query_message
+// query_time_sec, query_time_nsec
+
+// CLIENT_RESPONSE:
+// socket_family, socket_protocol
+// query_time_sec, query_time_nsec
+// response_message
+// response_time_sec, response_time_nsec
diff --git a/modules/dnstap/meson.build b/modules/dnstap/meson.build
new file mode 100644
index 0000000..e8a94bf
--- /dev/null
+++ b/modules/dnstap/meson.build
@@ -0,0 +1,57 @@
+# C module: dnstap
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+dnstap_src = files([
+ 'dnstap.c',
+])
+
+## dnstap dependencies
+build_dnstap = false
+if get_option('dnstap') != 'disabled'
+ dnstap_required = get_option('dnstap') == 'enabled'
+ message('--- dnstap module dependencies ---')
+ libprotobuf_c = dependency('libprotobuf-c', version: '>=1', required: dnstap_required)
+ libfstrm = dependency('libfstrm', version: '>=0.2', required: dnstap_required)
+ protoc_c = find_program('protoc-c', required: dnstap_required)
+ message('----------------------------------')
+ if libprotobuf_c.found() and libfstrm.found() and protoc_c.found()
+ build_dnstap = true
+ endif
+endif
+
+
+if build_dnstap
+ c_src_lint += dnstap_src
+
+ # generate protobuf-c sources using protoc-c
+ dnstap_pb = custom_target(
+ 'dnstap_pb',
+ command: [
+ protoc_c,
+ '--c_out=' + meson.current_build_dir(),
+ '--proto_path', meson.current_source_dir(),
+ meson.current_source_dir() / 'dnstap.proto',
+ ],
+ input: [ 'dnstap.proto' ],
+ output: [
+ 'dnstap.pb-c.h',
+ 'dnstap.pb-c.c',
+ ],
+ )
+
+ # build dnstap module
+ dnstap_mod = shared_module(
+ 'dnstap',
+ dnstap_src,
+ dependencies: [
+ declare_dependency(sources: dnstap_pb),
+ libfstrm,
+ libprotobuf_c,
+ libknot,
+ ],
+ include_directories: mod_inc_dir,
+ name_prefix: '',
+ install: true,
+ install_dir: modules_dir,
+ )
+endif
diff --git a/modules/edns_keepalive/.packaging/test.config b/modules/edns_keepalive/.packaging/test.config
new file mode 100644
index 0000000..5c71c79
--- /dev/null
+++ b/modules/edns_keepalive/.packaging/test.config
@@ -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 --git a/modules/edns_keepalive/README.rst b/modules/edns_keepalive/README.rst
new file mode 100644
index 0000000..d8034d2
--- /dev/null
+++ b/modules/edns_keepalive/README.rst
@@ -0,0 +1,22 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-edns_keepalive:
+
+EDNS keepalive
+==============
+
+The ``edns_keepalive`` module implements :rfc:`7828` for *clients*
+connecting to Knot Resolver via TCP and TLS.
+The module just allows clients to discover the connection timeout,
+client connections are always timed-out the same way *regardless*
+of clients sending the EDNS option.
+
+When connecting to servers, Knot Resolver does not send this EDNS option.
+It still attempts to reuse established connections intelligently.
+
+This module is loaded by default. For debugging purposes it can be
+unloaded using standard means:
+
+.. code-block:: lua
+
+ modules.unload('edns_keepalive')
diff --git a/modules/edns_keepalive/edns_keepalive.c b/modules/edns_keepalive/edns_keepalive.c
new file mode 100644
index 0000000..30d5df3
--- /dev/null
+++ b/modules/edns_keepalive/edns_keepalive.c
@@ -0,0 +1,61 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/**
+ * @file edns_keepalive.c
+ * @brief Minimalistic EDNS keepalive implementation on server side.
+ * If keepalive option is present in query,
+ * always reply with constant timeout value.
+ *
+ */
+#include <libknot/packet/pkt.h>
+#include "daemon/worker.h"
+#include "lib/module.h"
+#include "lib/layer.h"
+
+static int edns_keepalive_finalize(kr_layer_t *ctx)
+{
+ struct kr_request *req = ctx->req;
+ knot_pkt_t *answer = req->answer;
+ const knot_rrset_t *src_opt = req->qsource.packet->opt_rr;
+ knot_rrset_t *answ_opt = answer->opt_rr;
+
+ const bool ka_want =
+ req->qsource.flags.tcp &&
+ src_opt != NULL &&
+ knot_edns_get_option(src_opt, KNOT_EDNS_OPTION_TCP_KEEPALIVE, NULL) &&
+ answ_opt != NULL;
+ if (!ka_want) {
+ return ctx->state;
+ }
+ const struct network *net = &the_worker->engine->net;
+ uint64_t timeout = net->tcp.in_idle_timeout / 100;
+ if (timeout > UINT16_MAX) {
+ timeout = UINT16_MAX;
+ }
+ knot_mm_t *pool = &answer->mm;
+ uint16_t ka_size = knot_edns_keepalive_size(timeout);
+ uint8_t ka_buf[ka_size];
+ int ret = knot_edns_keepalive_write(ka_buf, ka_size, timeout);
+ if (ret == KNOT_EOK) {
+ ret = knot_edns_add_option(answ_opt, KNOT_EDNS_OPTION_TCP_KEEPALIVE,
+ ka_size, ka_buf, pool);
+ }
+ if (ret != KNOT_EOK) {
+ ctx->state = KR_STATE_FAIL;
+ }
+ return ctx->state;
+}
+
+KR_EXPORT int edns_keepalive_init(struct kr_module *self)
+{
+ static const kr_layer_api_t layer = {
+ .answer_finalize = &edns_keepalive_finalize,
+ };
+ self->layer = &layer;
+ return kr_ok();
+}
+
+KR_MODULE_EXPORT(edns_keepalive)
+
diff --git a/modules/edns_keepalive/meson.build b/modules/edns_keepalive/meson.build
new file mode 100644
index 0000000..979452f
--- /dev/null
+++ b/modules/edns_keepalive/meson.build
@@ -0,0 +1,17 @@
+# C module: edns_keepalive
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+edns_keepalive_src = files([
+ 'edns_keepalive.c',
+])
+c_src_lint += edns_keepalive_src
+
+edns_keepalive_mod = shared_module(
+ 'edns_keepalive',
+ edns_keepalive_src,
+ dependencies: libknot,
+ include_directories: mod_inc_dir,
+ name_prefix: '',
+ install: true,
+ install_dir: modules_dir,
+)
diff --git a/modules/etcd/.packaging/centos/7/pre-test.sh b/modules/etcd/.packaging/centos/7/pre-test.sh
new file mode 100755
index 0000000..4df79d9
--- /dev/null
+++ b/modules/etcd/.packaging/centos/7/pre-test.sh
@@ -0,0 +1 @@
+luarocks install etcd --from=https://mah0x211.github.io/rocks/
diff --git a/modules/etcd/.packaging/centos/7/rundeps b/modules/etcd/.packaging/centos/7/rundeps
new file mode 100644
index 0000000..795a3c4
--- /dev/null
+++ b/modules/etcd/.packaging/centos/7/rundeps
@@ -0,0 +1,6 @@
+openssl-devel
+lua-devel
+luarocks
+git
+gcc
+make
diff --git a/modules/etcd/.packaging/centos/8/NOTSUPPORTED b/modules/etcd/.packaging/centos/8/NOTSUPPORTED
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/modules/etcd/.packaging/centos/8/NOTSUPPORTED
diff --git a/modules/etcd/.packaging/debian/10/pre-test.sh b/modules/etcd/.packaging/debian/10/pre-test.sh
new file mode 100755
index 0000000..20073dc
--- /dev/null
+++ b/modules/etcd/.packaging/debian/10/pre-test.sh
@@ -0,0 +1 @@
+luarocks --lua-version 5.1 install etcd --from=https://mah0x211.github.io/rocks/
diff --git a/modules/etcd/.packaging/debian/10/rundeps b/modules/etcd/.packaging/debian/10/rundeps
new file mode 100644
index 0000000..02d3fcf
--- /dev/null
+++ b/modules/etcd/.packaging/debian/10/rundeps
@@ -0,0 +1,4 @@
+libssl-dev
+luarocks
+git
+make
diff --git a/modules/etcd/.packaging/debian/9/pre-test.sh b/modules/etcd/.packaging/debian/9/pre-test.sh
new file mode 100755
index 0000000..4df79d9
--- /dev/null
+++ b/modules/etcd/.packaging/debian/9/pre-test.sh
@@ -0,0 +1 @@
+luarocks install etcd --from=https://mah0x211.github.io/rocks/
diff --git a/modules/etcd/.packaging/debian/9/rundeps b/modules/etcd/.packaging/debian/9/rundeps
new file mode 100644
index 0000000..02d3fcf
--- /dev/null
+++ b/modules/etcd/.packaging/debian/9/rundeps
@@ -0,0 +1,4 @@
+libssl-dev
+luarocks
+git
+make
diff --git a/modules/etcd/.packaging/fedora/31/NOTSUPPORTED b/modules/etcd/.packaging/fedora/31/NOTSUPPORTED
new file mode 100644
index 0000000..b912289
--- /dev/null
+++ b/modules/etcd/.packaging/fedora/31/NOTSUPPORTED
@@ -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 --git a/modules/etcd/.packaging/fedora/32/NOTSUPPORTED b/modules/etcd/.packaging/fedora/32/NOTSUPPORTED
new file mode 100644
index 0000000..b912289
--- /dev/null
+++ b/modules/etcd/.packaging/fedora/32/NOTSUPPORTED
@@ -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 --git a/modules/etcd/.packaging/leap/15.2/pre-test.sh b/modules/etcd/.packaging/leap/15.2/pre-test.sh
new file mode 100755
index 0000000..20073dc
--- /dev/null
+++ b/modules/etcd/.packaging/leap/15.2/pre-test.sh
@@ -0,0 +1 @@
+luarocks --lua-version 5.1 install etcd --from=https://mah0x211.github.io/rocks/
diff --git a/modules/etcd/.packaging/leap/15.2/rundeps b/modules/etcd/.packaging/leap/15.2/rundeps
new file mode 100644
index 0000000..e8df59b
--- /dev/null
+++ b/modules/etcd/.packaging/leap/15.2/rundeps
@@ -0,0 +1,6 @@
+libopenssl-devel
+lua51-devel
+lua51-luarocks
+git
+gcc
+make
diff --git a/modules/etcd/.packaging/test.config b/modules/etcd/.packaging/test.config
new file mode 100644
index 0000000..1cc7e5a
--- /dev/null
+++ b/modules/etcd/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('etcd')
+assert(etcd)
+quit()
diff --git a/modules/etcd/.packaging/ubuntu/16.04/pre-test.sh b/modules/etcd/.packaging/ubuntu/16.04/pre-test.sh
new file mode 100755
index 0000000..4df79d9
--- /dev/null
+++ b/modules/etcd/.packaging/ubuntu/16.04/pre-test.sh
@@ -0,0 +1 @@
+luarocks install etcd --from=https://mah0x211.github.io/rocks/
diff --git a/modules/etcd/.packaging/ubuntu/16.04/rundeps b/modules/etcd/.packaging/ubuntu/16.04/rundeps
new file mode 100644
index 0000000..a355a9f
--- /dev/null
+++ b/modules/etcd/.packaging/ubuntu/16.04/rundeps
@@ -0,0 +1,3 @@
+libssl-dev
+luarocks
+git
diff --git a/modules/etcd/.packaging/ubuntu/18.04/pre-test.sh b/modules/etcd/.packaging/ubuntu/18.04/pre-test.sh
new file mode 100755
index 0000000..4df79d9
--- /dev/null
+++ b/modules/etcd/.packaging/ubuntu/18.04/pre-test.sh
@@ -0,0 +1 @@
+luarocks install etcd --from=https://mah0x211.github.io/rocks/
diff --git a/modules/etcd/.packaging/ubuntu/18.04/rundeps b/modules/etcd/.packaging/ubuntu/18.04/rundeps
new file mode 100644
index 0000000..a355a9f
--- /dev/null
+++ b/modules/etcd/.packaging/ubuntu/18.04/rundeps
@@ -0,0 +1,3 @@
+libssl-dev
+luarocks
+git
diff --git a/modules/etcd/.packaging/ubuntu/20.04/pre-test.sh b/modules/etcd/.packaging/ubuntu/20.04/pre-test.sh
new file mode 100755
index 0000000..20073dc
--- /dev/null
+++ b/modules/etcd/.packaging/ubuntu/20.04/pre-test.sh
@@ -0,0 +1 @@
+luarocks --lua-version 5.1 install etcd --from=https://mah0x211.github.io/rocks/
diff --git a/modules/etcd/.packaging/ubuntu/20.04/rundeps b/modules/etcd/.packaging/ubuntu/20.04/rundeps
new file mode 100644
index 0000000..02d3fcf
--- /dev/null
+++ b/modules/etcd/.packaging/ubuntu/20.04/rundeps
@@ -0,0 +1,4 @@
+libssl-dev
+luarocks
+git
+make
diff --git a/modules/etcd/README.rst b/modules/etcd/README.rst
new file mode 100644
index 0000000..0ffa781
--- /dev/null
+++ b/modules/etcd/README.rst
@@ -0,0 +1,46 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-etcd:
+
+Etcd support
+------------
+
+The `etcd` module connects to `etcd <https://etcd.io/>`_ peers and watches
+for configuration changes. By default, the module watches the subtree under
+``/knot-resolver`` directory, but you can change this in the
+`etcd library configuration <https://github.com/mah0x211/lua-etcd#cli-err--etcdnew-optiontable->`_.
+
+The subtree structure corresponds to the configuration variables in the declarative style.
+
+.. code-block:: bash
+
+ $ etcdctl set /knot-resolver/net/127.0.0.1 53
+ $ etcdctl set /knot-resolver/cache/size 10000000
+
+Configures all listening nodes to following configuration:
+
+.. code-block:: lua
+
+ net = { '127.0.0.1' }
+ cache.size = 10000000
+
+Example configuration
+^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: lua
+
+ modules.load('etcd')
+ etcd.config({
+ prefix = '/knot-resolver',
+ peer = 'http://127.0.0.1:7001'
+ })
+
+.. warning:: Work in progress!
+
+Dependencies
+^^^^^^^^^^^^
+
+* `lua-etcd <https://github.com/mah0x211/lua-etcd>`_ library available in LuaRocks
+
+ ``$ luarocks --lua-version 5.1 install etcd --from=https://mah0x211.github.io/rocks/``
+
diff --git a/modules/etcd/etcd.lua b/modules/etcd/etcd.lua
new file mode 100644
index 0000000..ab58024
--- /dev/null
+++ b/modules/etcd/etcd.lua
@@ -0,0 +1,56 @@
+--- @module etcd
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local etcd = {}
+
+-- @function update subtree configuration
+local function update_subtree(tree)
+ if not tree then return end
+ for _, k in pairs(tree) do
+ if k.dir then
+ update_subtree(k.nodes)
+ else
+ local key,opt = k.key:gmatch('([^/]+)/([^/]+)$')()
+ if _G[key][opt] ~= k.value then
+ _G[key][opt] = k.value
+ end
+ end
+ end
+end
+
+-- @function reload whole configuration
+function etcd.reload()
+ local res, err = etcd.cli:readdir('/', true)
+ if err then
+ error(err)
+ end
+ update_subtree(res.body.node.nodes)
+end
+
+function etcd.init()
+ etcd.Etcd = require('etcd.luasocket')
+ etcd.defaults = { prefix = '/knot-resolver' }
+end
+
+function etcd.deinit()
+ if etcd.ev then event.cancel(etcd.ev) end
+end
+
+function etcd.config(conf)
+ local options = etcd.defaults
+ if type(conf) == 'table' then
+ for k,v in pairs(conf) do options[k] = v end
+ end
+ -- create connection
+ local cli, err = etcd.Etcd.new(options)
+ if err then
+ error(err)
+ end
+ etcd.cli = cli
+ -- schedule recurrent polling
+ -- @todo: the etcd has watch() API, but this requires
+ -- coroutines on socket operations
+ if etcd.ev then event.cancel(etcd.ev) end
+ etcd.ev = event.recurrent(5 * sec, etcd.reload)
+end
+
+return etcd
diff --git a/modules/experimental_dot_auth/.packaging/centos/7/rundeps b/modules/experimental_dot_auth/.packaging/centos/7/rundeps
new file mode 100644
index 0000000..36b83e1
--- /dev/null
+++ b/modules/experimental_dot_auth/.packaging/centos/7/rundeps
@@ -0,0 +1 @@
+lua-basexx
diff --git a/modules/experimental_dot_auth/.packaging/centos/8/rundeps b/modules/experimental_dot_auth/.packaging/centos/8/rundeps
new file mode 100644
index 0000000..984c7ce
--- /dev/null
+++ b/modules/experimental_dot_auth/.packaging/centos/8/rundeps
@@ -0,0 +1 @@
+lua5.1-basexx
diff --git a/modules/experimental_dot_auth/.packaging/debian/10/rundeps b/modules/experimental_dot_auth/.packaging/debian/10/rundeps
new file mode 100644
index 0000000..36b83e1
--- /dev/null
+++ b/modules/experimental_dot_auth/.packaging/debian/10/rundeps
@@ -0,0 +1 @@
+lua-basexx
diff --git a/modules/experimental_dot_auth/.packaging/debian/9/rundeps b/modules/experimental_dot_auth/.packaging/debian/9/rundeps
new file mode 100644
index 0000000..36b83e1
--- /dev/null
+++ b/modules/experimental_dot_auth/.packaging/debian/9/rundeps
@@ -0,0 +1 @@
+lua-basexx
diff --git a/modules/experimental_dot_auth/.packaging/fedora/31/rundeps b/modules/experimental_dot_auth/.packaging/fedora/31/rundeps
new file mode 100644
index 0000000..984c7ce
--- /dev/null
+++ b/modules/experimental_dot_auth/.packaging/fedora/31/rundeps
@@ -0,0 +1 @@
+lua5.1-basexx
diff --git a/modules/experimental_dot_auth/.packaging/fedora/32/rundeps b/modules/experimental_dot_auth/.packaging/fedora/32/rundeps
new file mode 100644
index 0000000..984c7ce
--- /dev/null
+++ b/modules/experimental_dot_auth/.packaging/fedora/32/rundeps
@@ -0,0 +1 @@
+lua5.1-basexx
diff --git a/modules/experimental_dot_auth/.packaging/leap/15.2/NOTSUPPORTED b/modules/experimental_dot_auth/.packaging/leap/15.2/NOTSUPPORTED
new file mode 100644
index 0000000..682eff0
--- /dev/null
+++ b/modules/experimental_dot_auth/.packaging/leap/15.2/NOTSUPPORTED
@@ -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 --git a/modules/experimental_dot_auth/.packaging/leap/15.2/pre-test.sh b/modules/experimental_dot_auth/.packaging/leap/15.2/pre-test.sh
new file mode 100755
index 0000000..df5d784
--- /dev/null
+++ b/modules/experimental_dot_auth/.packaging/leap/15.2/pre-test.sh
@@ -0,0 +1 @@
+luarocks --lua-version 5.1 install basexx --from=https://mah0x211.github.io/rocks/
diff --git a/modules/experimental_dot_auth/.packaging/leap/15.2/rundeps b/modules/experimental_dot_auth/.packaging/leap/15.2/rundeps
new file mode 100644
index 0000000..9e636d8
--- /dev/null
+++ b/modules/experimental_dot_auth/.packaging/leap/15.2/rundeps
@@ -0,0 +1,4 @@
+lua51-luarocks
+git
+tar
+gzip
diff --git a/modules/experimental_dot_auth/.packaging/test.config b/modules/experimental_dot_auth/.packaging/test.config
new file mode 100644
index 0000000..39e9aed
--- /dev/null
+++ b/modules/experimental_dot_auth/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('experimental_dot_auth')
+assert(experimental_dot_auth)
+quit()
diff --git a/modules/experimental_dot_auth/.packaging/ubuntu/16.04/NOTSUPPORTED b/modules/experimental_dot_auth/.packaging/ubuntu/16.04/NOTSUPPORTED
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/modules/experimental_dot_auth/.packaging/ubuntu/16.04/NOTSUPPORTED
diff --git a/modules/experimental_dot_auth/.packaging/ubuntu/18.04/rundeps b/modules/experimental_dot_auth/.packaging/ubuntu/18.04/rundeps
new file mode 100644
index 0000000..36b83e1
--- /dev/null
+++ b/modules/experimental_dot_auth/.packaging/ubuntu/18.04/rundeps
@@ -0,0 +1 @@
+lua-basexx
diff --git a/modules/experimental_dot_auth/.packaging/ubuntu/20.04/rundeps b/modules/experimental_dot_auth/.packaging/ubuntu/20.04/rundeps
new file mode 100644
index 0000000..36b83e1
--- /dev/null
+++ b/modules/experimental_dot_auth/.packaging/ubuntu/20.04/rundeps
@@ -0,0 +1 @@
+lua-basexx
diff --git a/modules/experimental_dot_auth/README.rst b/modules/experimental_dot_auth/README.rst
new file mode 100644
index 0000000..a93f516
--- /dev/null
+++ b/modules/experimental_dot_auth/README.rst
@@ -0,0 +1,91 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-experimental_dot_auth:
+
+Experimental DNS-over-TLS Auto-discovery
+========================================
+
+This experimental module provides automatic discovery of authoritative servers' supporting DNS-over-TLS.
+The module uses magic NS names to detect SPKI_ fingerprint which is very similar to `dnscurve`_ mechanism.
+
+.. warning:: This protocol and module is experimental and can be changed or removed at any time. Use at own risk, security properties were not analyzed!
+
+How it works
+------------
+
+The module will look for NS target names formatted as:
+``dot-{base32(sha256(SPKI))}....``
+
+For instance, Knot Resolver will detect NS names formatted like this
+
+.. code-block:: none
+
+ example.com NS dot-tpwxmgqdaurcqxqsckxvdq5sty3opxlgcbjj43kumdq62kpqr72a.example.com
+
+and automatically discover that example.com NS supports DoT with the base64-encoded SPKI digest of ``m+12GgMFIiheEhKvUcOynjbn3WYQUp5tVGDh7Snwj/Q=``
+and will associate it with the IPs of ``dot-tpwxmgqdaurcqxqsckxvdq5sty3opxlgcbjj43kumdq62kpqr72a.example.com``.
+
+In that example, the base32 encoded (no padding) version of the sha256 PIN is ``tpwxmgqdaurcqxqsckxvdq5sty3opxlgcbjj43kumdq62kpqr72a``, which when
+converted to base64 translates to ``m+12GgMFIiheEhKvUcOynjbn3WYQUp5tVGDh7Snwj/Q=``.
+
+Generating NS target names
+--------------------------
+
+To generate the NS target name, use the following command to generate the base32 encoded string of the SPKI fingerprint:
+
+.. code-block:: bash
+
+ openssl x509 -in /path/to/cert.pem -pubkey -noout | \
+ openssl pkey -pubin -outform der | \
+ openssl dgst -sha256 -binary | \
+ base32 | tr -d '=' | tr '[:upper:]' '[:lower:]'
+ tpwxmgqdaurcqxqsckxvdq5sty3opxlgcbjj43kumdq62kpqr72a
+
+Then add a target to your NS with: ``dot-${b32}.a.example.com``
+
+Finally, map ``dot-${b32}.a.example.com`` to the right set of IPs.
+
+.. code-block:: bash
+
+ ...
+ ...
+ ;; QUESTION SECTION:
+ ;example.com. IN NS
+
+ ;; AUTHORITY SECTION:
+ example.com. 3600 IN NS dot-tpwxmgqdaurcqxqsckxvdq5sty3opxlgcbjj43kumdq62kpqr72a.a.example.com.
+ example.com. 3600 IN NS dot-tpwxmgqdaurcqxqsckxvdq5sty3opxlgcbjj43kumdq62kpqr72a.b.example.com.
+
+ ;; ADDITIONAL SECTION:
+ dot-tpwxmgqdaurcqxqsckxvdq5sty3opxlgcbjj43kumdq62kpqr72a.a.example.com. 3600 IN A 192.0.2.1
+ dot-tpwxmgqdaurcqxqsckxvdq5sty3opxlgcbjj43kumdq62kpqr72a.b.example.com. 3600 IN AAAA 2001:DB8::1
+ ...
+ ...
+
+Example configuration
+---------------------
+
+To enable the module, add this snippet to your config:
+
+.. code-block:: lua
+
+ -- Start an experiment, use with caution
+ modules.load('experimental_dot_auth')
+
+This module requires standard ``basexx`` Lua library which is typically provided by ``lua-basexx`` package.
+
+Caveats
+-------
+
+The module relies on seeing the reply of the NS query and as such will not work
+if Knot Resolver uses data from its cache. You may need to delete the cache before starting ``kresd`` to work around this.
+
+The module also assumes that the NS query answer will return both the NS targets in the Authority section as well as the glue records in the Additional section.
+
+Dependencies
+------------
+
+* `lua-basexx <https://github.com/aiq/basexx>`_ available in LuaRocks
+
+.. _dnscurve: https://dnscurve.org/
+.. _SPKI: https://en.wikipedia.org/wiki/Simple_public-key_infrastructure
diff --git a/modules/experimental_dot_auth/experimental_dot_auth.lua b/modules/experimental_dot_auth/experimental_dot_auth.lua
new file mode 100644
index 0000000..08c5080
--- /dev/null
+++ b/modules/experimental_dot_auth/experimental_dot_auth.lua
@@ -0,0 +1,122 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- Module interface
+
+local ffi = require('ffi')
+local basexx = require('basexx')
+local C = ffi.C
+
+-- Export module interface
+local M = {}
+M.layer = {}
+local base32 = {}
+local str = {}
+local AF_INET = 2
+local AF_INET6 = 10
+local INET_ADDRSTRLEN = 16
+local INET6_ADDRSTRLEN = 46
+
+ffi.cdef[[
+/*
+ * Data structures
+ */
+typedef int socklen_t;
+ struct sockaddr_storage{
+ unsigned short int ss_family;
+ unsigned long int __ss_align;
+ char __ss_padding[128 - (2 *sizeof(unsigned long int))];
+ };
+ struct in_addr{
+ unsigned char s_addr[4];
+ };
+ struct in6_addr{
+ unsigned char s6_addr[16];
+ };
+ struct sockaddr_in{
+ short sin_family;
+ unsigned short sin_port;
+ struct in_addr sin_addr;
+ char sin_zero[8];
+ } __attribute__ ((__packed__));
+ struct sockaddr_in6{
+ unsigned short sin6_family;
+ unsigned short sin6_port;
+ unsigned int sin6_flowinfo;
+ struct in6_addr sin6_addr;
+ unsigned int sin6_scope_id;
+ };
+ typedef unsigned short sa_family_t;
+ struct sockaddr_un {
+ sa_family_t sun_family;
+ char sun_path[108];
+ };
+ const char *inet_ntop(
+ int af,
+ const void *cp,
+ char *buf,
+ socklen_t len);
+]]
+
+function base32.pad(b32)
+ local m = #b32 % 8
+ if m ~= 0 then
+ b32 = b32 .. string.rep("=", 8 - m)
+ end
+ return b32
+end
+
+function str.starts(String,Start)
+ return string.sub(String,1,string.len(Start))==Start
+end
+
+-- Handle DoT signalling NS domains.
+function M.layer.consume(state, _, pkt)
+ -- Only successful answers
+ if state == kres.FAIL then return state end
+ -- log_debug(ffi.C.LOG_GRP_DOTAUTH, "%s", pkt:tostring())
+ local authority = pkt:section(kres.section.AUTHORITY)
+ local additional = pkt:section(kres.section.ADDITIONAL)
+ for _, rr in ipairs(authority) do
+ --log_debug(ffi.C.LOG_GRP_DOTAUTH, "%d %s", rr.type, kres.dname2str(rr.rdata))
+ if rr.type == kres.type.NS then
+ local name = kres.dname2str(rr.rdata):upper()
+ -- log_debug(ffi.C.LOG_GRP_DOTAUTH, "NS %d", name:len())
+ if name:len() > 56 and str.starts(name, "DOT-") then
+ local k = basexx.to_base64(
+ basexx.from_base32(
+ base32.pad(string.sub(name, 5, string.find(name, '[.]') - 1))
+ )
+ )
+ for _, rr_add in ipairs(additional) do
+ if rr_add.type == kres.type.A or rr_add.type == kres.type.AAAA then
+ local name_add = kres.dname2str(rr_add.owner):upper()
+ if name == name_add then
+ local addrbuf
+ if rr_add.type == kres.type.A then
+ local ns_addr = ffi.new("struct sockaddr_in")
+ ns_addr.sin_family = AF_INET
+
+ ns_addr.sin_addr.s_addr = rr_add.rdata
+ addrbuf = ffi.new("char[?]", INET_ADDRSTRLEN)
+ C.inet_ntop(AF_INET, ns_addr.sin_addr, addrbuf, INET_ADDRSTRLEN)
+ else
+ local ns_addr = ffi.new("struct sockaddr_in6")
+ ns_addr.sin6_family = AF_INET6
+
+ ns_addr.sin6_addr.s6_addr = rr_add.rdata
+ addrbuf = ffi.new("char[?]", INET6_ADDRSTRLEN)
+ C.inet_ntop(AF_INET6, ns_addr.sin6_addr, addrbuf, INET6_ADDRSTRLEN)
+ end
+ net.tls_client(ffi.string(addrbuf).."@853", {k})
+ log_info(ffi.C.LOG_GRP_DOTAUTH, "Adding %s IP %s %s", name_add, ffi.string(addrbuf).."@853", k)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ return state
+
+end
+
+return M
diff --git a/modules/experimental_dot_auth/meson.build b/modules/experimental_dot_auth/meson.build
new file mode 100644
index 0000000..e2e1edf
--- /dev/null
+++ b/modules/experimental_dot_auth/meson.build
@@ -0,0 +1,13 @@
+# LUA module: experimental_dot_auth
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+lua_mod_src += [
+ files('experimental_dot_auth.lua'),
+]
+
+# install static files
+install_subdir(
+ 'static',
+ strip_directory: true,
+ install_dir: modules_dir / 'http',
+)
diff --git a/modules/extended_error/extended_error.c b/modules/extended_error/extended_error.c
new file mode 100644
index 0000000..db0ed57
--- /dev/null
+++ b/modules/extended_error/extended_error.c
@@ -0,0 +1,47 @@
+#include <libknot/rrtype/opt.h>
+
+#include "lib/module.h"
+#include "daemon/engine.h"
+
+static int extended_error_finalize(kr_layer_t *ctx) {
+ struct kr_request *req = ctx->req;
+ const knot_rrset_t *src_opt = req->qsource.packet->opt_rr;
+ const struct kr_extended_error *ede = &req->extended_error;
+
+ if (ede->info_code == KNOT_EDNS_EDE_NONE /* no extended error */
+ || src_opt == NULL /* no EDNS in query */
+ || kr_fails_assert(ede->info_code >= 0 && ede->info_code < UINT16_MAX) /* info code out of range */
+ || kr_fails_assert(req->answer->opt_rr) /* sanity check - answer should have EDNS */
+ ) {
+ return ctx->state;
+ }
+
+ const uint16_t info_code = (uint16_t)ede->info_code;
+ const size_t extra_len = ede->extra_text ? strlen(ede->extra_text) : 0;
+ uint8_t buf[sizeof(info_code) + extra_len];
+ knot_wire_write_u16(buf, info_code);
+ if (extra_len)
+ memcpy(buf + sizeof(info_code), ede->extra_text, extra_len);
+
+ if (knot_edns_add_option(req->answer->opt_rr, KNOT_EDNS_OPTION_EDE,
+ sizeof(buf), buf, &req->pool) != KNOT_EOK) {
+ /* something went wrong and there is no way to salvage content of OPT RRset */
+ kr_log_req(req, 0, 0, EDE, "unable to add Extended Error option\n");
+ knot_rrset_clear(req->answer->opt_rr, &req->pool);
+ }
+
+ return ctx->state;
+}
+
+KR_EXPORT
+int extended_error_init(struct kr_module *module) {
+ static kr_layer_api_t layer = {
+ .answer_finalize = &extended_error_finalize,
+ };
+ layer.data = module;
+ module->layer = &layer;
+
+ return kr_ok();
+}
+
+KR_MODULE_EXPORT(extended_error)
diff --git a/modules/extended_error/meson.build b/modules/extended_error/meson.build
new file mode 100644
index 0000000..26e87b0
--- /dev/null
+++ b/modules/extended_error/meson.build
@@ -0,0 +1,20 @@
+# C module: extended_error
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+extended_error_src = files([
+ 'extended_error.c',
+])
+c_src_lint += extended_error_src
+
+extended_error_mod = shared_module(
+ 'extended_error',
+ extended_error_src,
+ dependencies: [
+ libknot,
+ luajit_inc,
+ ],
+ include_directories: mod_inc_dir,
+ name_prefix: '',
+ install: true,
+ install_dir: modules_dir,
+)
diff --git a/modules/graphite/.packaging/centos/7/rundeps b/modules/graphite/.packaging/centos/7/rundeps
new file mode 100644
index 0000000..3da806b
--- /dev/null
+++ b/modules/graphite/.packaging/centos/7/rundeps
@@ -0,0 +1 @@
+lua-cqueues
diff --git a/modules/graphite/.packaging/centos/8/rundeps b/modules/graphite/.packaging/centos/8/rundeps
new file mode 100644
index 0000000..182251d
--- /dev/null
+++ b/modules/graphite/.packaging/centos/8/rundeps
@@ -0,0 +1 @@
+lua5.1-cqueues
diff --git a/modules/graphite/.packaging/debian/10/rundeps b/modules/graphite/.packaging/debian/10/rundeps
new file mode 100644
index 0000000..3da806b
--- /dev/null
+++ b/modules/graphite/.packaging/debian/10/rundeps
@@ -0,0 +1 @@
+lua-cqueues
diff --git a/modules/graphite/.packaging/debian/9/rundeps b/modules/graphite/.packaging/debian/9/rundeps
new file mode 100644
index 0000000..3da806b
--- /dev/null
+++ b/modules/graphite/.packaging/debian/9/rundeps
@@ -0,0 +1 @@
+lua-cqueues
diff --git a/modules/graphite/.packaging/fedora/31/rundeps b/modules/graphite/.packaging/fedora/31/rundeps
new file mode 100644
index 0000000..182251d
--- /dev/null
+++ b/modules/graphite/.packaging/fedora/31/rundeps
@@ -0,0 +1 @@
+lua5.1-cqueues
diff --git a/modules/graphite/.packaging/fedora/32/rundeps b/modules/graphite/.packaging/fedora/32/rundeps
new file mode 100644
index 0000000..182251d
--- /dev/null
+++ b/modules/graphite/.packaging/fedora/32/rundeps
@@ -0,0 +1 @@
+lua5.1-cqueues
diff --git a/modules/graphite/.packaging/leap/15.2/NOTSUPPORTED b/modules/graphite/.packaging/leap/15.2/NOTSUPPORTED
new file mode 100644
index 0000000..b1ae77d
--- /dev/null
+++ b/modules/graphite/.packaging/leap/15.2/NOTSUPPORTED
@@ -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 --git a/modules/graphite/.packaging/leap/15.2/pre-test.sh b/modules/graphite/.packaging/leap/15.2/pre-test.sh
new file mode 100755
index 0000000..9614066
--- /dev/null
+++ b/modules/graphite/.packaging/leap/15.2/pre-test.sh
@@ -0,0 +1 @@
+luarocks --lua-version 5.1 install cqueues --from=https://mah0x211.github.io/rocks/
diff --git a/modules/graphite/.packaging/leap/15.2/rundeps b/modules/graphite/.packaging/leap/15.2/rundeps
new file mode 100644
index 0000000..8323887
--- /dev/null
+++ b/modules/graphite/.packaging/leap/15.2/rundeps
@@ -0,0 +1,6 @@
+libopenssl-devel
+lua51-luarocks
+git
+tar
+gzip
+m4
diff --git a/modules/graphite/.packaging/test.config b/modules/graphite/.packaging/test.config
new file mode 100644
index 0000000..c23033b
--- /dev/null
+++ b/modules/graphite/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('graphite')
+assert(graphite)
+quit()
diff --git a/modules/graphite/.packaging/ubuntu/16.04/rundeps b/modules/graphite/.packaging/ubuntu/16.04/rundeps
new file mode 100644
index 0000000..3da806b
--- /dev/null
+++ b/modules/graphite/.packaging/ubuntu/16.04/rundeps
@@ -0,0 +1 @@
+lua-cqueues
diff --git a/modules/graphite/.packaging/ubuntu/18.04/rundeps b/modules/graphite/.packaging/ubuntu/18.04/rundeps
new file mode 100644
index 0000000..3da806b
--- /dev/null
+++ b/modules/graphite/.packaging/ubuntu/18.04/rundeps
@@ -0,0 +1 @@
+lua-cqueues
diff --git a/modules/graphite/.packaging/ubuntu/20.04/rundeps b/modules/graphite/.packaging/ubuntu/20.04/rundeps
new file mode 100644
index 0000000..3da806b
--- /dev/null
+++ b/modules/graphite/.packaging/ubuntu/20.04/rundeps
@@ -0,0 +1 @@
+lua-cqueues
diff --git a/modules/graphite/README.rst b/modules/graphite/README.rst
new file mode 100644
index 0000000..2f86a6f
--- /dev/null
+++ b/modules/graphite/README.rst
@@ -0,0 +1,49 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-graphite:
+
+Graphite/InfluxDB/Metronome
+---------------------------
+
+The ``graphite`` sends statistics over the Graphite_ protocol to either Graphite_, Metronome_, InfluxDB_ or any compatible storage. This allows powerful visualization over metrics collected by Knot Resolver.
+
+.. tip:: The Graphite server is challenging to get up and running, InfluxDB_ combined with Grafana_ are much easier, and provide richer set of options and available front-ends. Metronome_ by PowerDNS alternatively provides a mini-graphite server for much simpler setups.
+
+Example configuration:
+
+Only the ``host`` parameter is mandatory.
+
+By default the module uses UDP so it doesn't guarantee the delivery, set ``tcp = true`` to enable Graphite over TCP. If the TCP consumer goes down or the connection with Graphite is lost, resolver will periodically attempt to reconnect with it.
+
+.. code-block:: lua
+
+ modules = {
+ graphite = {
+ 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 you want TCP mode
+ }
+ }
+
+The module supports sending data to multiple servers at once.
+
+.. code-block:: lua
+
+ modules = {
+ graphite = {
+ host = { '127.0.0.1', '1.2.3.4', '::1' },
+ }
+ }
+
+Dependencies
+^^^^^^^^^^^^
+
+* `lua cqueues <https://25thandclement.com/~william/projects/cqueues.html>`_ package.
+
+
+.. _Graphite: https://graphite.readthedocs.io/en/latest/feeding-carbon.html
+.. _InfluxDB: https://influxdb.com/
+.. _Metronome: https://github.com/ahuPowerDNS/metronome
+.. _Grafana: http://grafana.org/
diff --git a/modules/graphite/graphite.lua b/modules/graphite/graphite.lua
new file mode 100644
index 0000000..be2d7b2
--- /dev/null
+++ b/modules/graphite/graphite.lua
@@ -0,0 +1,146 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- Load dependent modules
+if not stats then modules.load('stats') end
+
+-- This is leader-only module
+local M = {}
+local ffi = require("ffi")
+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')
+ 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_info(ffi.C.LOG_GRP_GRAPHITE, 'connecting: %s@%d %s reason: %s',
+ host, port, proto_txt[stype], err)
+ return status, err
+ end
+ return s
+end
+
+-- Create connected UDP socket
+local function make_udp(host, port)
+ return make_socket(host, port, socket.SOCK_DGRAM)
+end
+
+-- Create connected TCP socket
+local function make_tcp(host, port)
+ return make_socket(host, port, socket.SOCK_STREAM)
+end
+
+-- Send the metrics in a table to multiple Graphite consumers
+local function publish_table(metrics, prefix, now)
+ 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
+
+ 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
+
+ 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_info(ffi.C.LOG_GRP_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 -- loop M.cli
+end
+
+function M.init()
+ M.ev = nil
+ M.cli = {}
+ M.info = {}
+ M.interval = 5 * sec
+ M.prefix = string.format('kresd.%s.%s', hostname(), worker.id)
+ return 0
+end
+
+function M.deinit()
+ if M.ev then event.cancel(M.ev) end
+ return 0
+end
+
+-- @function Publish results to the Graphite server(s)
+function M.publish()
+ local now = os.time()
+ -- Publish built-in statistics
+ if not M.cli then error("no graphite server configured") end
+ publish_table(cache.stats(), M.prefix..'.cache', now)
+ publish_table(worker.stats(), M.prefix..'.worker', now)
+ -- Publish extended statistics if available
+ publish_table(stats.list(), M.prefix, now)
+ return 0
+end
+
+-- @function Make connection to Graphite server.
+function M.add_server(_, host, port, tcp)
+ table.insert(M.cli, -1)
+ table.insert(M.info, {addr = host, port = port, tcp = tcp, seen = 0})
+ return 0
+end
+
+function M.config(conf)
+ -- config defaults
+ if not conf then return 0 end
+ 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
+ if type(conf.host) == 'table' then
+ for _, val in pairs(conf.host) do
+ M:add_server(val, conf.port, conf.tcp)
+ end
+ else
+ M:add_server(conf.host, conf.port, conf.tcp)
+ end
+ -- start publishing stats
+ if M.ev then event.cancel(M.ev) end
+ M.ev = event.recurrent(M.interval, function() worker.coroutine(M.publish) end)
+ return 0
+end
+
+return M
diff --git a/modules/hints/.packaging/test.config b/modules/hints/.packaging/test.config
new file mode 100644
index 0000000..d89c7f0
--- /dev/null
+++ b/modules/hints/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('hints')
+assert(hints)
+quit()
diff --git a/modules/hints/README.rst b/modules/hints/README.rst
new file mode 100644
index 0000000..97d24dd
--- /dev/null
+++ b/modules/hints/README.rst
@@ -0,0 +1,145 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-hints:
+
+Static hints
+============
+
+This is a module providing static hints for forward records (A/AAAA) and reverse records (PTR).
+The records can be loaded from ``/etc/hosts``-like files and/or added directly.
+
+You can also use the module to change the root hints; they are used as a safety belt or if the root NS
+drops out of cache.
+
+.. tip::
+
+ For blocking large lists of domains please use :func:`policy.rpz`
+ instead of creating huge list of domains with IP address *0.0.0.0*.
+
+Examples
+--------
+
+.. code-block:: lua
+
+ -- Load hints after iterator (so hints take precedence before caches)
+ modules = { 'hints > iterate' }
+ -- Add a custom hosts file
+ hints.add_hosts('hosts.custom')
+ -- Override the root hints
+ hints.root({
+ ['j.root-servers.net.'] = { '2001:503:c27::2:30', '192.58.128.30' }
+ })
+ -- Add a custom hint
+ hints['foo.bar'] = '127.0.0.1'
+
+.. note::
+ The :ref:`policy <mod-policy>` module applies before hints,
+ so your hints might get surprisingly shadowed by even default policies.
+
+ That most often happens for :rfc:`6761#section-6` names, e.g.
+ ``localhost`` and ``test`` or with ``PTR`` records in private address ranges.
+ To unblock the required names, you may use an explicit :any:`policy.PASS` action.
+
+ .. code-block:: lua
+
+ policy.add(policy.suffix(policy.PASS, {todname('1.168.192.in-addr.arpa')}))
+
+ This ``.PASS`` workaround isn't ideal. To improve some cases,
+ we recommend to move these ``.PASS`` lines to the end of your rule list.
+ The point is that applying any :ref:`non-chain action <mod-policy-actions>`
+ (e.g. :ref:`forwarding actions <forwarding>` or ``.PASS`` itself)
+ stops processing *any* later policy rules for that request (including the default block-rules).
+ You probably don't want this ``.PASS`` to shadow any other rules you might have;
+ and on the other hand, if any other non-chain rule triggers,
+ additional ``.PASS`` would not change anything even if it were somehow force-executed.
+
+Properties
+----------
+
+.. function:: hints.config([path])
+
+ :param string path: path to hosts-like file, default: no file
+ :return: ``{ result: bool }``
+
+ Clear any configured hints, and optionally load a hosts-like file as in ``hints.add_hosts(path)``.
+ (Root hints are not touched.)
+
+.. function:: hints.add_hosts([path])
+
+ :param string path: path to hosts-like file, default: ``/etc/hosts``
+
+ Add hints from a host-like file.
+
+.. function:: hints.get(hostname)
+
+ :param string hostname: i.e. ``"localhost"``
+ :return: ``{ result: [address1, address2, ...] }``
+
+ Return list of address record matching given name.
+ If no hostname is specified, all hints are returned in the table format used by ``hints.root()``.
+
+.. function:: hints.set(pair)
+
+ :param string pair: ``hostname address`` i.e. ``"localhost 127.0.0.1"``
+ :return: ``{ result: bool }``
+
+ Add a hostname--address pair hint.
+
+ .. note::
+
+ If multiple addresses have been added for a name (in separate ``hints.set()`` commands),
+ all are returned in a forward query.
+ If multiple names have been added to an address, the last one defined is returned
+ in a corresponding PTR query.
+
+.. function:: hints.del(pair)
+
+ :param string pair: ``hostname address`` i.e. ``"localhost 127.0.0.1"``, or just ``hostname``
+ :return: ``{ result: bool }``
+
+ Remove a hostname - address pair hint. If address is omitted, all addresses for the given name are deleted.
+
+.. function:: hints.root_file(path)
+
+ Replace current root hints from a zonefile. If the path is omitted, the compiled-in path is used, i.e. the root hints are reset to the default.
+
+.. function:: hints.root(root_hints)
+
+ :param table root_hints: new set of root hints i.e. ``{['name'] = 'addr', ...}``
+ :return: ``{ ['a.root-servers.net.'] = { '1.2.3.4', '5.6.7.8', ...}, ... }``
+
+ Replace current root hints and return the current table of root hints.
+
+ .. tip:: If no parameters are passed, it only returns current root hints set without changing anything.
+
+ Example:
+
+ .. code-block:: lua
+
+ > hints.root({
+ ['l.root-servers.net.'] = '199.7.83.42',
+ ['m.root-servers.net.'] = '202.12.27.33'
+ })
+ [l.root-servers.net.] => {
+ [1] => 199.7.83.42
+ }
+ [m.root-servers.net.] => {
+ [1] => 202.12.27.33
+ }
+
+ .. tip:: A good rule of thumb is to select only a few fastest root hints. The server learns RTT and NS quality over time, and thus tries all servers available. You can help it by preselecting the candidates.
+
+.. function:: hints.use_nodata(toggle)
+
+ :param bool toggle: true if enabling NODATA synthesis, false if disabling
+ :return: ``{ result: bool }``
+
+ If set to true (the default), NODATA will be synthesised for matching hint name, but mismatching type (e.g. AAAA query when only A hint exists).
+
+.. function:: hints.ttl([new_ttl])
+
+ :param int new_ttl: new TTL to set (optional)
+ :return: the TTL setting
+
+ This function allows to read and write the TTL value used for records generated by the hints module.
+
diff --git a/modules/hints/hints.c b/modules/hints/hints.c
new file mode 100644
index 0000000..34c08b9
--- /dev/null
+++ b/modules/hints/hints.c
@@ -0,0 +1,677 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/**
+ * @file hints.c
+ * @brief Constructed zone cut from the hosts-like file, see @zonecut.h
+ *
+ * The module provides an override for queried address records.
+ */
+
+#include <libknot/packet/pkt.h>
+#include <libknot/descriptor.h>
+#include <ccan/json/json.h>
+#include <ucw/mempool.h>
+#include <contrib/cleanup.h>
+#include <lauxlib.h>
+
+#include "daemon/engine.h"
+#include "lib/zonecut.h"
+#include "lib/module.h"
+#include "lib/layer.h"
+
+#include <inttypes.h>
+#include <math.h>
+
+/* Defaults */
+#define VERBOSE_MSG(qry, ...) kr_log_q(qry, HINT, __VA_ARGS__)
+#define ERR_MSG(...) kr_log_error(HINT, "[ ]" __VA_ARGS__)
+
+struct hints_data {
+ struct kr_zonecut hints;
+ struct kr_zonecut reverse_hints;
+ bool use_nodata; /**< See hint_use_nodata() description, exposed via lua. */
+ uint32_t ttl; /**< TTL used for the hints, exposed via lua. */
+};
+static const uint32_t HINTS_TTL_DEFAULT = 5;
+
+/** Useful for returning from module properties. */
+static char * bool2jsonstr(bool val)
+{
+ char *result = NULL;
+ if (-1 == asprintf(&result, "{ \"result\": %s }", val ? "true" : "false"))
+ result = NULL;
+ return result;
+}
+
+static int put_answer(knot_pkt_t *pkt, struct kr_query *qry, knot_rrset_t *rr, bool use_nodata)
+{
+ int ret = 0;
+ if (!knot_rrset_empty(rr) || use_nodata) {
+ /* Update packet question */
+ if (!knot_dname_is_equal(knot_pkt_qname(pkt), rr->owner)) {
+ kr_pkt_recycle(pkt);
+ knot_pkt_put_question(pkt, qry->sname, qry->sclass, qry->stype);
+ }
+ if (!knot_rrset_empty(rr)) {
+ /* Append to packet */
+ ret = knot_pkt_put_rotate(pkt, KNOT_COMPR_HINT_QNAME, rr,
+ qry->reorder, KNOT_PF_FREE);
+ } else {
+ /* Return empty answer if name exists, but type doesn't match */
+ knot_wire_set_aa(pkt->wire);
+ }
+ } else {
+ ret = kr_error(ENOENT);
+ }
+ /* Clear RR if failed */
+ if (ret != 0) {
+ knot_rrset_clear(rr, &pkt->mm);
+ }
+ return ret;
+}
+
+static int satisfy_reverse(/*const*/ struct hints_data *data,
+ knot_pkt_t *pkt, struct kr_query *qry)
+{
+ /* Find a matching name */
+ pack_t *addr_set = kr_zonecut_find(&data->reverse_hints, qry->sname);
+ if (!addr_set || addr_set->len == 0) {
+ return kr_error(ENOENT);
+ }
+ knot_dname_t *qname = knot_dname_copy(qry->sname, &pkt->mm);
+ knot_rrset_t rr;
+ knot_rrset_init(&rr, qname, KNOT_RRTYPE_PTR, KNOT_CLASS_IN, data->ttl);
+
+ /* Append address records from hints */
+ uint8_t *addr = pack_last(*addr_set);
+ if (addr != NULL) {
+ size_t len = pack_obj_len(addr);
+ void *addr_val = pack_obj_val(addr);
+ knot_rrset_add_rdata(&rr, addr_val, len, &pkt->mm);
+ }
+
+ return put_answer(pkt, qry, &rr, data->use_nodata);
+}
+
+static int satisfy_forward(/*const*/ struct hints_data *data,
+ knot_pkt_t *pkt, struct kr_query *qry)
+{
+ /* Find a matching name */
+ pack_t *addr_set = kr_zonecut_find(&data->hints, qry->sname);
+ if (!addr_set || addr_set->len == 0) {
+ return kr_error(ENOENT);
+ }
+ 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;
+ 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);
+ while (addr != pack_tail(*addr_set)) {
+ size_t len = pack_obj_len(addr);
+ void *addr_val = pack_obj_val(addr);
+ if (len == family_len) {
+ knot_rrset_add_rdata(&rr, addr_val, len, &pkt->mm);
+ }
+ addr = pack_obj_next(addr);
+ }
+finish:
+ return put_answer(pkt, qry, &rr, data->use_nodata);
+}
+
+static int query(kr_layer_t *ctx, knot_pkt_t *pkt)
+{
+ struct kr_query *qry = ctx->req->current_query;
+ if (!qry || (ctx->state & KR_STATE_FAIL)) {
+ return ctx->state;
+ }
+
+ struct kr_module *module = ctx->api->data;
+ struct hints_data *data = module->data;
+ 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. */
+ if (knot_dname_in_bailiwick(qry->sname, (const uint8_t *)"\4arpa\0") >= 0) {
+ if (satisfy_reverse(data, pkt, qry) != 0)
+ return ctx->state;
+ } else {
+ if (satisfy_forward(data, pkt, qry) != 0)
+ return ctx->state;
+ }
+
+ VERBOSE_MSG(qry, "<= answered from hints\n");
+ qry->flags.DNSSEC_WANT = false; /* Never authenticated */
+ qry->flags.CACHED = true;
+ qry->flags.NO_MINIMIZE = true;
+ pkt->parsed = pkt->size;
+ knot_wire_set_qr(pkt->wire);
+ return KR_STATE_DONE;
+}
+
+static int parse_addr_str(union kr_sockaddr *sa, const char *addr)
+{
+ int family = strchr(addr, ':') ? AF_INET6 : AF_INET;
+ memset(sa, 0, sizeof(*sa));
+ sa->ip.sa_family = family;
+ char *addr_bytes = (/*const*/char *)kr_inaddr(&sa->ip);
+ if (inet_pton(family, addr, addr_bytes) != 1) {
+ return kr_error(EILSEQ);
+ }
+ return 0;
+}
+
+/** @warning _NOT_ thread-safe; returns a pointer to static data! */
+static const knot_dname_t * raw_addr2reverse(const uint8_t *raw_addr, int family)
+{
+ #define REV_MAXLEN (4*16 + 16 /* the suffix, terminator, etc. */)
+ char reverse_addr[REV_MAXLEN];
+ static knot_dname_t dname[REV_MAXLEN];
+ #undef REV_MAXLEN
+
+ if (family == AF_INET) {
+ snprintf(reverse_addr, sizeof(reverse_addr),
+ "%d.%d.%d.%d.in-addr.arpa.",
+ raw_addr[3], raw_addr[2], raw_addr[1], raw_addr[0]);
+ } else if (family == AF_INET6) {
+ char *ra_it = reverse_addr;
+ for (int i = 15; i >= 0; --i) {
+ ssize_t free_space = reverse_addr + sizeof(reverse_addr) - ra_it;
+ int written = snprintf(ra_it, free_space, "%x.%x.",
+ raw_addr[i] & 0x0f, raw_addr[i] >> 4);
+ if (kr_fails_assert(written < free_space))
+ return NULL;
+ ra_it += written;
+ }
+ ssize_t free_space = reverse_addr + sizeof(reverse_addr) - ra_it;
+ if (snprintf(ra_it, free_space, "ip6.arpa.") >= free_space) {
+ return NULL;
+ }
+ } else {
+ return NULL;
+ }
+
+ if (!knot_dname_from_str(dname, reverse_addr, sizeof(dname))) {
+ return NULL;
+ }
+ return dname;
+}
+
+static const knot_dname_t * addr2reverse(const char *addr)
+{
+ /* Parse address string */
+ union kr_sockaddr ia;
+ if (parse_addr_str(&ia, addr) != 0) {
+ return NULL;
+ }
+ return raw_addr2reverse((const /*sign*/uint8_t *)kr_inaddr(&ia.ip),
+ kr_inaddr_family(&ia.ip));
+}
+
+static int add_pair(struct kr_zonecut *hints, const char *name, const char *addr)
+{
+ /* Build key */
+ knot_dname_t key[KNOT_DNAME_MAXLEN];
+ if (!knot_dname_from_str(key, name, sizeof(key))) {
+ return kr_error(EINVAL);
+ }
+ knot_dname_to_lower(key);
+
+ union kr_sockaddr ia;
+ if (parse_addr_str(&ia, addr) != 0) {
+ return kr_error(EINVAL);
+ }
+
+ return kr_zonecut_add(hints, key, kr_inaddr(&ia.ip), kr_inaddr_len(&ia.ip));
+}
+
+static int add_reverse_pair(struct kr_zonecut *hints, const char *name, const char *addr)
+{
+ const knot_dname_t *key = addr2reverse(addr);
+
+ if (key == NULL) {
+ return kr_error(EINVAL);
+ }
+
+ knot_dname_t ptr_name[KNOT_DNAME_MAXLEN];
+ if (!knot_dname_from_str(ptr_name, name, sizeof(ptr_name))) {
+ return kr_error(EINVAL);
+ }
+
+ return kr_zonecut_add(hints, key, ptr_name, knot_dname_size(ptr_name));
+}
+
+/** For a given name, remove either one address or all of them (if == NULL).
+ *
+ * Also remove the corresponding reverse records.
+ */
+static int del_pair(struct hints_data *data, const char *name, const char *addr)
+{
+ /* Build key */
+ knot_dname_t key[KNOT_DNAME_MAXLEN];
+ if (!knot_dname_from_str(key, name, sizeof(key))) {
+ return kr_error(EINVAL);
+ }
+ int key_len = knot_dname_size(key);
+
+ if (addr) {
+ /* Remove the pair. */
+ union kr_sockaddr ia;
+ if (parse_addr_str(&ia, addr) != 0) {
+ return kr_error(EINVAL);
+ }
+
+ const knot_dname_t *reverse_key = addr2reverse(addr);
+ kr_zonecut_del(&data->reverse_hints, reverse_key, key, key_len);
+ return kr_zonecut_del(&data->hints, key,
+ kr_inaddr(&ia.ip), kr_inaddr_len(&ia.ip));
+ }
+ /* We're removing everything for the name;
+ * first find the name's pack */
+ pack_t *addr_set = kr_zonecut_find(&data->hints, key);
+ if (!addr_set || addr_set->len == 0) {
+ return kr_error(ENOENT);
+ }
+
+ /* Remove address records in hints from reverse_hints. */
+
+ for (uint8_t *a = pack_head(*addr_set); a != pack_tail(*addr_set);
+ a = pack_obj_next(a)) {
+ void *addr_val = pack_obj_val(a);
+ int family = pack_obj_len(a) == kr_family_len(AF_INET)
+ ? AF_INET : AF_INET6;
+ const knot_dname_t *reverse_key = raw_addr2reverse(addr_val, family);
+ if (reverse_key != NULL) {
+ kr_zonecut_del(&data->reverse_hints, reverse_key, key, key_len);
+ }
+ }
+
+ /* Remove the whole name. */
+ return kr_zonecut_del_all(&data->hints, key);
+}
+
+static int load_file(struct kr_module *module, const char *path)
+{
+ auto_fclose FILE *fp = fopen(path, "r");
+ if (fp == NULL) {
+ ERR_MSG("reading '%s' failed: %s\n", path, strerror(errno));
+ return kr_error(errno);
+ } else {
+ VERBOSE_MSG(NULL, "reading '%s'\n", path);
+ }
+
+ /* Load file to map */
+ struct hints_data *data = module->data;
+ size_t line_len_unused = 0;
+ size_t count = 0;
+ size_t line_count = 0;
+ auto_free char *line = NULL;
+ int ret = kr_ok();
+
+ while (getline(&line, &line_len_unused, fp) > 0) {
+ ++line_count;
+ /* Ingore #comments as described in man hosts.5 */
+ char *comm = strchr(line, '#');
+ if (comm) {
+ *comm = '\0';
+ }
+
+ char *saveptr = NULL;
+ const char *addr = strtok_r(line, " \t\n", &saveptr);
+ if (addr == NULL || strlen(addr) == 0) {
+ continue;
+ }
+ const char *canonical_name = strtok_r(NULL, " \t\n", &saveptr);
+ if (canonical_name == NULL) {
+ ret = -1;
+ goto error;
+ }
+ /* Since the last added PTR records takes preference,
+ * we add canonical name as the last one. */
+ const char *name_tok;
+ while ((name_tok = strtok_r(NULL, " \t\n", &saveptr)) != NULL) {
+ ret = add_pair(&data->hints, name_tok, addr);
+ if (!ret) {
+ ret = add_reverse_pair(&data->reverse_hints, name_tok, addr);
+ }
+ if (ret) {
+ ret = -1;
+ goto error;
+ }
+ count += 1;
+ }
+ ret = add_pair(&data->hints, canonical_name, addr);
+ if (!ret) {
+ ret = add_reverse_pair(&data->reverse_hints, canonical_name, addr);
+ }
+ if (ret) {
+ ret = -1;
+ goto error;
+ }
+ count += 1;
+ }
+error:
+ if (ret) {
+ ret = kr_error(ret);
+ ERR_MSG("%s:%zu: invalid syntax\n", path, line_count);
+ }
+ VERBOSE_MSG(NULL, "loaded %zu hints\n", count);
+ return ret;
+}
+
+static char* hint_add_hosts(void *env, struct kr_module *module, const char *args)
+{
+ if (!args)
+ args = "/etc/hosts";
+ int err = load_file(module, args);
+ return bool2jsonstr(err == kr_ok());
+}
+
+/**
+ * Set name => address hint.
+ *
+ * Input: { name, address }
+ * Output: { result: bool }
+ *
+ */
+static char* hint_set(void *env, struct kr_module *module, const char *args)
+{
+ struct hints_data *data = module->data;
+ if (!args)
+ return NULL;
+ auto_free char *args_copy = strdup(args);
+ if (!args_copy)
+ return NULL;
+
+ int ret = -1;
+ char *addr = strchr(args_copy, ' ');
+ if (addr) {
+ *addr = '\0';
+ ++addr;
+ ret = add_reverse_pair(&data->reverse_hints, args_copy, addr);
+ if (ret) {
+ del_pair(data, args_copy, addr);
+ } else {
+ ret = add_pair(&data->hints, args_copy, addr);
+ }
+ }
+
+ return bool2jsonstr(ret == 0);
+}
+
+static char* hint_del(void *env, struct kr_module *module, const char *args)
+{
+ struct hints_data *data = module->data;
+ if (!args)
+ return NULL;
+ auto_free char *args_copy = strdup(args);
+ if (!args_copy)
+ return NULL;
+
+ int ret = -1;
+ char *addr = strchr(args_copy, ' ');
+ if (addr) {
+ *addr = '\0';
+ ++addr;
+ }
+ ret = del_pair(data, args_copy, addr);
+
+ return bool2jsonstr(ret == 0);
+}
+
+/** @internal Pack address list into JSON array. */
+static JsonNode *pack_addrs(pack_t *pack)
+{
+ char buf[INET6_ADDRSTRLEN];
+ JsonNode *root = json_mkarray();
+ uint8_t *addr = pack_head(*pack);
+ while (addr != pack_tail(*pack)) {
+ size_t len = pack_obj_len(addr);
+ int family = len == sizeof(struct in_addr) ? AF_INET : AF_INET6;
+ if (!inet_ntop(family, pack_obj_val(addr), buf, sizeof(buf))) {
+ break;
+ }
+ json_append_element(root, json_mkstring(buf));
+ addr = pack_obj_next(addr);
+ }
+ return root;
+}
+
+static char* pack_hints(struct kr_zonecut *hints);
+/**
+ * Retrieve address hints, either for given name or for all names.
+ *
+ * Input: name
+ * Output: NULL or "{ address1, address2, ... }"
+ */
+static char* hint_get(void *env, struct kr_module *module, const char *args)
+{
+ struct kr_zonecut *hints = &((struct hints_data *) module->data)->hints;
+ if (kr_fails_assert(hints))
+ return NULL;
+
+ if (!args) {
+ return pack_hints(hints);
+ }
+
+ knot_dname_t key[KNOT_DNAME_MAXLEN];
+ pack_t *pack = NULL;
+ if (knot_dname_from_str(key, args, sizeof(key))) {
+ pack = kr_zonecut_find(hints, key);
+ }
+ if (!pack || pack->len == 0) {
+ return NULL;
+ }
+
+ char *result = NULL;
+ JsonNode *root = pack_addrs(pack);
+ if (root) {
+ result = json_encode(root);
+ json_delete(root);
+ }
+ return result;
+}
+
+/** @internal Pack all hints into serialized JSON. */
+static char* pack_hints(struct kr_zonecut *hints) {
+ char *result = NULL;
+ JsonNode *root_node = json_mkobject();
+ trie_it_t *it;
+ for (it = trie_it_begin(hints->nsset); !trie_it_finished(it); trie_it_next(it)) {
+ KR_DNAME_GET_STR(nsname_str, (const knot_dname_t *)trie_it_key(it, NULL));
+ JsonNode *addr_list = pack_addrs((pack_t *)*trie_it_val(it));
+ if (!addr_list) goto error;
+ json_append_member(root_node, nsname_str, addr_list);
+ }
+ result = json_encode(root_node);
+error:
+ trie_it_free(it);
+ json_delete(root_node);
+ return result;
+}
+
+static void unpack_hint(struct kr_zonecut *root_hints, JsonNode *table, const char *name)
+{
+ JsonNode *node = NULL;
+ json_foreach(node, table) {
+ switch(node->tag) {
+ case JSON_STRING: add_pair(root_hints, name ? name : node->key, node->string_); break;
+ case JSON_ARRAY: unpack_hint(root_hints, node, name ? name : node->key); break;
+ default: continue;
+ }
+ }
+}
+
+/**
+ * Get/set root hints set.
+ *
+ * Input: { name: [addr_list], ... }
+ * Output: current list
+ *
+ */
+static char* hint_root(void *env, struct kr_module *module, const char *args)
+{
+ struct engine *engine = env;
+ struct kr_context *ctx = &engine->resolver;
+ struct kr_zonecut *root_hints = &ctx->root_hints;
+ /* Replace root hints if parameter is set */
+ if (args && args[0] != '\0') {
+ JsonNode *root_node = json_decode(args);
+ kr_zonecut_set(root_hints, (const uint8_t *)"");
+ unpack_hint(root_hints, root_node, NULL);
+ json_delete(root_node);
+ }
+ /* Return current root hints */
+ return pack_hints(root_hints);
+}
+
+static char* hint_root_file(void *env, struct kr_module *module, const char *args)
+{
+ struct engine *engine = env;
+ struct kr_context *ctx = &engine->resolver;
+ const char *err_msg = engine_hint_root_file(ctx, args);
+ if (err_msg) {
+ luaL_error(engine->L, "error when opening '%s': %s", args, err_msg);
+ }
+ return strdup(err_msg ? err_msg : "");
+}
+
+static char* hint_use_nodata(void *env, struct kr_module *module, const char *args)
+{
+ struct hints_data *data = module->data;
+ if (!args) {
+ return NULL;
+ }
+
+ JsonNode *root_node = json_decode(args);
+ if (!root_node || root_node->tag != JSON_BOOL) {
+ json_delete(root_node);
+ return bool2jsonstr(false);
+ }
+
+ data->use_nodata = root_node->bool_;
+ json_delete(root_node);
+ return bool2jsonstr(true);
+}
+
+static char* hint_ttl(void *env, struct kr_module *module, const char *args)
+{
+ struct hints_data *data = module->data;
+
+ /* Do no change on nonsense TTL values (incl. suspicious floats). */
+ JsonNode *root_node = args ? json_decode(args) : NULL;
+ if (root_node && root_node->tag == JSON_NUMBER) {
+ double ttl_d = root_node->number_;
+ uint32_t ttl = (uint32_t)round(ttl_d);
+ if (ttl_d >= 0 && fabs(ttl_d - ttl) * 64 < 1) {
+ data->ttl = ttl;
+ }
+ }
+ json_delete(root_node);
+
+ /* Always return the current TTL setting. Plain number is valid JSON. */
+ char *result = NULL;
+ if (-1 == asprintf(&result, "%"PRIu32, data->ttl)) {
+ result = NULL;
+ }
+ return result;
+}
+
+/** Basic initialization: get a memory pool, etc. */
+KR_EXPORT
+int hints_init(struct kr_module *module)
+{
+ static kr_layer_api_t layer = {
+ .produce = &query,
+ };
+ /* Store module reference */
+ layer.data = module;
+ module->layer = &layer;
+
+ static const struct kr_prop props[] = {
+ { &hint_set, "set", "Set {name, address} hint.", },
+ { &hint_del, "del", "Delete one {name, address} hint or all addresses for the name.", },
+ { &hint_get, "get", "Retrieve hint for given name.", },
+ { &hint_ttl, "ttl", "Set/get TTL used for the hints.", },
+ { &hint_add_hosts, "add_hosts", "Load a file with hosts-like formatting and add contents into hints.", },
+ { &hint_root, "root", "Replace root hints set (empty value to return current list).", },
+ { &hint_root_file, "root_file", "Replace root hints set from a zonefile.", },
+ { &hint_use_nodata, "use_nodata", "Synthesise NODATA if name matches, but type doesn't. True by default.", },
+ { NULL, NULL, NULL }
+ };
+ module->props = props;
+
+ knot_mm_t *pool = mm_ctx_mempool2(MM_DEFAULT_BLKSIZE);
+ if (!pool) {
+ return kr_error(ENOMEM);
+ }
+ struct hints_data *data = mm_alloc(pool, sizeof(struct hints_data));
+ if (!data) {
+ mp_delete(pool->ctx);
+ return kr_error(ENOMEM);
+ }
+ kr_zonecut_init(&data->hints, (const uint8_t *)(""), pool);
+ kr_zonecut_init(&data->reverse_hints, (const uint8_t *)(""), pool);
+ data->use_nodata = true;
+ data->ttl = HINTS_TTL_DEFAULT;
+ module->data = data;
+
+ return kr_ok();
+}
+
+/** Release all resources. */
+KR_EXPORT
+int hints_deinit(struct kr_module *module)
+{
+ struct hints_data *data = module->data;
+ if (data) {
+ kr_zonecut_deinit(&data->hints);
+ kr_zonecut_deinit(&data->reverse_hints);
+ mp_delete(data->hints.pool->ctx);
+ module->data = NULL;
+ }
+ return kr_ok();
+}
+
+/** Drop all hints, and load a hosts file if any was specified.
+ *
+ * It seems slightly strange to drop all, but keep doing that for now.
+ */
+KR_EXPORT
+int hints_config(struct kr_module *module, const char *conf)
+{
+ hints_deinit(module);
+ int err = hints_init(module);
+ if (err != kr_ok()) {
+ return err;
+ }
+
+ if (conf && conf[0]) {
+ return load_file(module, conf);
+ }
+ return kr_ok();
+}
+
+KR_MODULE_EXPORT(hints)
+
+#undef VERBOSE_MSG
diff --git a/modules/hints/meson.build b/modules/hints/meson.build
new file mode 100644
index 0000000..0a0945c
--- /dev/null
+++ b/modules/hints/meson.build
@@ -0,0 +1,24 @@
+# C module: hints
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+hints_src = files([
+ 'hints.c',
+])
+c_src_lint += hints_src
+
+hints_mod = shared_module(
+ 'hints',
+ hints_src,
+ dependencies: [
+ libknot,
+ luajit_inc,
+ ],
+ include_directories: mod_inc_dir,
+ name_prefix: '',
+ install: true,
+ install_dir: modules_dir,
+)
+
+config_tests += [
+ ['hints', files('tests/hints.test.lua'), ['skip_asan']],
+]
diff --git a/modules/hints/tests/hints.test.hosts b/modules/hints/tests/hints.test.hosts
new file mode 100644
index 0000000..0111507
--- /dev/null
+++ b/modules/hints/tests/hints.test.hosts
@@ -0,0 +1 @@
+192.0.2.1 myname.lan # badname.lan and the rest of the comment
diff --git a/modules/hints/tests/hints.test.lua b/modules/hints/tests/hints.test.lua
new file mode 100644
index 0000000..b62e502
--- /dev/null
+++ b/modules/hints/tests/hints.test.lua
@@ -0,0 +1,64 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local utils = require('test_utils')
+
+-- setup resolver
+modules = { 'hints > iterate' }
+
+-- test for default configuration
+local function test_default()
+ -- get loaded root hints and change names to lowercase
+ local hints_data = utils.table_keys_to_lower(hints.root())
+
+ -- root hints loaded from default location
+ -- check correct ip address of a.root-server.net
+ utils.contains(hints_data['a.root-servers.net.'], '198.41.0.4', 'has IP address for a.root-servers.net.')
+end
+
+-- test loading from config file
+local function test_custom()
+ -- load custom root hints file with fake ip address for a.root-server.net
+ local err_msg = hints.root_file('hints_test.zone')
+ same(err_msg, '', 'load root hints from file')
+
+ -- get loaded root hints and change names to lowercase
+ local hints_data = utils.table_keys_to_lower(hints.root())
+ isnt(hints_data['a.root-servers.net.'], nil, 'can retrieve root hints')
+
+ -- check loaded ip address of a.root-server.net
+ utils.not_contains(hints_data['a.root-servers.net.'], '198.41.0.4',
+ 'real IP address for a.root-servers.net. is replaced')
+ utils.contains(hints_data['a.root-servers.net.'], '10.0.0.1',
+ '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.add_hosts('hints.test.hosts')
+ -- 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_nxdomain,
+ test_nodata,
+}
diff --git a/modules/hints/tests/hints_test.zone b/modules/hints/tests/hints_test.zone
new file mode 100644
index 0000000..c3252f8
--- /dev/null
+++ b/modules/hints/tests/hints_test.zone
@@ -0,0 +1,2 @@
+; SPDX-License-Identifier: GPL-3.0-or-later
+A.ROOT-SERVERS.NET. 3600000 A 10.0.0.1
diff --git a/modules/http/.packaging/centos/7/rundeps b/modules/http/.packaging/centos/7/rundeps
new file mode 100644
index 0000000..c557cb2
--- /dev/null
+++ b/modules/http/.packaging/centos/7/rundeps
@@ -0,0 +1 @@
+lua-http
diff --git a/modules/http/.packaging/centos/8/rundeps b/modules/http/.packaging/centos/8/rundeps
new file mode 100644
index 0000000..ed5aee1
--- /dev/null
+++ b/modules/http/.packaging/centos/8/rundeps
@@ -0,0 +1 @@
+lua5.1-http
diff --git a/modules/http/.packaging/debian/10/rundeps b/modules/http/.packaging/debian/10/rundeps
new file mode 100644
index 0000000..c557cb2
--- /dev/null
+++ b/modules/http/.packaging/debian/10/rundeps
@@ -0,0 +1 @@
+lua-http
diff --git a/modules/http/.packaging/debian/9/rundeps b/modules/http/.packaging/debian/9/rundeps
new file mode 100644
index 0000000..c557cb2
--- /dev/null
+++ b/modules/http/.packaging/debian/9/rundeps
@@ -0,0 +1 @@
+lua-http
diff --git a/modules/http/.packaging/fedora/31/rundeps b/modules/http/.packaging/fedora/31/rundeps
new file mode 100644
index 0000000..ed5aee1
--- /dev/null
+++ b/modules/http/.packaging/fedora/31/rundeps
@@ -0,0 +1 @@
+lua5.1-http
diff --git a/modules/http/.packaging/fedora/32/rundeps b/modules/http/.packaging/fedora/32/rundeps
new file mode 100644
index 0000000..ed5aee1
--- /dev/null
+++ b/modules/http/.packaging/fedora/32/rundeps
@@ -0,0 +1 @@
+lua5.1-http
diff --git a/modules/http/.packaging/leap/15.2/NOTSUPPORTED b/modules/http/.packaging/leap/15.2/NOTSUPPORTED
new file mode 100644
index 0000000..bb50260
--- /dev/null
+++ b/modules/http/.packaging/leap/15.2/NOTSUPPORTED
@@ -0,0 +1,5 @@
+
+https://github.com/wahern/luaossl/issues/175
+
+
+Doesn't work with libopenssl-devel 1.1.0i-lp151.1.1
diff --git a/modules/http/.packaging/leap/15.2/pre-test.sh b/modules/http/.packaging/leap/15.2/pre-test.sh
new file mode 100755
index 0000000..bb1e131
--- /dev/null
+++ b/modules/http/.packaging/leap/15.2/pre-test.sh
@@ -0,0 +1 @@
+luarocks --lua-version 5.1 install http --from=https://mah0x211.github.io/rocks/
diff --git a/modules/http/.packaging/leap/15.2/rundeps b/modules/http/.packaging/leap/15.2/rundeps
new file mode 100644
index 0000000..ab05188
--- /dev/null
+++ b/modules/http/.packaging/leap/15.2/rundeps
@@ -0,0 +1,7 @@
+libopenssl-devel
+lua51-devel
+lua51-luarocks
+git
+tar
+gzip
+m4
diff --git a/modules/http/.packaging/test.config b/modules/http/.packaging/test.config
new file mode 100644
index 0000000..cb5e5dd
--- /dev/null
+++ b/modules/http/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('http')
+assert(http)
+quit()
diff --git a/modules/http/.packaging/ubuntu/16.04/NOTSUPPORTED b/modules/http/.packaging/ubuntu/16.04/NOTSUPPORTED
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/modules/http/.packaging/ubuntu/16.04/NOTSUPPORTED
diff --git a/modules/http/.packaging/ubuntu/18.04/rundeps b/modules/http/.packaging/ubuntu/18.04/rundeps
new file mode 100644
index 0000000..c557cb2
--- /dev/null
+++ b/modules/http/.packaging/ubuntu/18.04/rundeps
@@ -0,0 +1 @@
+lua-http
diff --git a/modules/http/.packaging/ubuntu/20.04/rundeps b/modules/http/.packaging/ubuntu/20.04/rundeps
new file mode 100644
index 0000000..c557cb2
--- /dev/null
+++ b/modules/http/.packaging/ubuntu/20.04/rundeps
@@ -0,0 +1 @@
+lua-http
diff --git a/modules/http/README.rst b/modules/http/README.rst
new file mode 100644
index 0000000..6d8a075
--- /dev/null
+++ b/modules/http/README.rst
@@ -0,0 +1,188 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-http:
+
+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
+ for openSUSE.
+
+This module does the heavy lifting to provide an HTTP and HTTP/2 enabled
+server which provides few built-in services and also allows other
+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.
+
+By default this module provides two kinds of endpoints,
+and unlimited number of "used-defined kinds" can be added in configuration.
+
++--------------+---------------------------------------------------------------------------------+
+| **Kind** | **Explanation** |
++--------------+---------------------------------------------------------------------------------+
+| webmgmt | :ref:`built-in web management <mod-http-built-in-services>` APIs (includes DoH) |
++--------------+---------------------------------------------------------------------------------+
+| doh_legacy | :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
+network configuration for plain DNS and DNS-over-TLS,
+see chapter :ref:`network-configuration` for more details.
+
+.. warning:: Management endpoint (``webmgmt``) must not be directly exposed
+ to untrusted parties. Use `reverse-proxy`_ like Apache_
+ or Nginx_ if you need to authenticate API clients
+ for the management API.
+
+By default all endpoints share the same configuration for TLS certificates etc.
+This can be changed using ``http.config()`` configuration call explained below.
+
+.. _mod-http-example:
+
+Example configuration
+---------------------
+
+This section shows how to configure HTTP module itself. For information how
+to configure HTTP server's IP addresses and ports please see chapter
+:ref:`network-configuration`.
+
+.. code-block:: lua
+
+ -- load HTTP module with defaults (self-signed TLS cert)
+ modules.load('http')
+ -- optionally load geoIP database for server map
+ http.config({
+ geoip = 'GeoLite2-City.mmdb',
+ -- e.g. https://dev.maxmind.com/geoip/geoip2/geolite2/
+ -- and install mmdblua library
+ })
+
+Now you can reach the web services and APIs, done!
+
+.. code-block:: bash
+
+ $ curl -k https://localhost:8453
+ $ curl -k https://localhost:8453/stats
+
+.. _mod-http-tls:
+
+HTTPS (TLS for HTTP)
+--------------------
+
+By default, the web interface starts HTTPS/2 on specified port using an ephemeral
+TLS certificate that is valid for 90 days and is automatically renewed. It is of
+course self-signed. Why not use something like
+`Let's Encrypt <https://letsencrypt.org>`_?
+
+.. warning::
+
+ If you use package ``luaossl < 20181207``, intermediate certificate is not sent to clients,
+ which may cause problems with validating the connection in some cases.
+
+You can disable unencrypted HTTP and enforce HTTPS by passing
+``tls = true`` option for all HTTP endpoints:
+
+.. code-block:: lua
+
+ http.config({
+ tls = true,
+ })
+
+It is also possible to provide different configuration for each
+kind of endpoint, e.g. to enforce TLS and use custom certificate only for DoH:
+
+.. code-block:: lua
+
+ http.config({
+ tls = true,
+ cert = '/etc/knot-resolver/mycert.crt',
+ key = '/etc/knot-resolver/mykey.key',
+ }, 'doh_legacy')
+
+The format of both certificate and key is expected to be PEM, e.g. equivalent to
+the outputs of following:
+
+.. code-block:: bash
+
+ openssl ecparam -genkey -name prime256v1 -out mykey.key
+ openssl req -new -key mykey.key -out csr.pem
+ openssl req -x509 -days 90 -key mykey.key -in csr.pem -out mycert.crt
+
+It is also possible to disable HTTPS altogether by passing ``tls = false`` option.
+Plain HTTP gets handy if you want to use `reverse-proxy`_ like Apache_ or Nginx_
+for authentication to API etc.
+(Unencrypted HTTP could be fine for localhost tests as, for example,
+Safari doesn't allow WebSockets over HTTPS with a self-signed certificate.
+Major drawback is that current browsers won't do HTTP/2 over insecure connection.)
+
+.. warning::
+
+ If you use multiple Knot Resolver instances with these automatically maintained ephemeral certificates,
+ 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_legacy'``)
+ 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 can be configured using
+``doh_legacy`` kind in :func:`net.listen`. Its configuration (such as certificates)
+takes place in ``http.config()``.
+
+Queries were served on ``/doh`` and ``/dns-query`` endpoints.
+
+.. _mod-http-built-in-services:
+
+Built-in services
+-----------------
+
+The HTTP module has several built-in services to use.
+
+.. csv-table::
+ :header: "Endpoint", "Service", "Description"
+
+ "``/stats``", "Statistics/metrics", "Exported :ref:`metrics <mod-stats-list>` from :ref:`mod-stats` in JSON format."
+ "``/metrics``", "Prometheus metrics", "Exported metrics for Prometheus_."
+ "``/trace/:name/:type``", "Tracking", ":ref:`Trace resolution <mod-http-trace>` of a DNS query and return its debug-level logs."
+ "``/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
+------------
+
+* `lua-http <https://github.com/daurnimator/lua-http>`_ (>= 0.3) available in LuaRocks
+
+ If you're installing via Homebrew on OS X, you need OpenSSL too.
+
+ .. code-block:: bash
+
+ $ brew update
+ $ brew install openssl
+ $ brew link openssl --force # Override system OpenSSL
+
+ Some other systems can install from LuaRocks directly:
+
+ .. code-block:: bash
+
+ $ luarocks --lua-version 5.1 install http
+
+* (*optional*) `mmdblua <https://github.com/daurnimator/mmdblua>`_ available in LuaRocks
+
+ .. code-block:: bash
+
+ $ 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
+
+.. _Prometheus: https://prometheus.io
+.. _reverse-proxy: https://en.wikipedia.org/wiki/Reverse_proxy
+.. _Apache: https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html
+.. _Nginx: https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/
diff --git a/modules/http/custom_services.rst b/modules/http/custom_services.rst
new file mode 100644
index 0000000..09ba5ab
--- /dev/null
+++ b/modules/http/custom_services.rst
@@ -0,0 +1,145 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-http-custom-endpoint:
+
+Custom HTTP services
+====================
+
+This chapter describes how to create custom HTTP services inside Knot Resolver.
+Please read HTTP module basics in chapter :ref:`mod-http` before continuing.
+
+Each network address+protocol+port combination configured using :func:`net.listen`
+is associated with *kind* of endpoint, e.g. ``doh_legacy`` or ``webmgmt``.
+
+Each of these *kind* names is associated with table of HTTP endpoints,
+and the default table can be replaced using ``http.config()`` configuration call
+which allows your to provide your own HTTP endpoints.
+
+Items in the table of HTTP endpoints are small tables describing a triplet
+- ``{mime, on_serve, on_websocket}``.
+In order to register a new service in ``webmgmt`` *kind* of HTTP endpoint
+add the new endpoint description to respective table:
+
+.. code-block:: lua
+
+ -- custom function to handle HTTP /health requests
+ local on_health = {'application/json',
+ function (h, stream)
+ -- API call, return a JSON table
+ return {state = 'up', uptime = 0}
+ end,
+ function (h, ws)
+ -- Stream current status every second
+ local ok = true
+ while ok do
+ local push = tojson('up')
+ ok = ws:send(tojson({'up'}))
+ require('cqueues').sleep(1)
+ end
+ -- Finalize the WebSocket
+ ws:close()
+ end}
+
+ modules.load('http')
+ -- copy all existing webmgmt endpoints
+ my_mgmt_endpoints = http.configs._builtin.webmgmt.endpoints
+ -- add custom endpoint to the copy
+ my_mgmt_endpoints['/health'] = on_health
+ -- use custom HTTP configuration for webmgmt
+ http.config({
+ endpoints = my_mgmt_endpoints
+ }, 'webmgmt')
+
+Then you can query the API endpoint, or tail the WebSocket using curl.
+
+.. code-block:: bash
+
+ $ curl -k https://localhost:8453/health
+ {"state":"up","uptime":0}
+ $ curl -k -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Host: localhost:8453/health" -H "Sec-Websocket-Key: nope" -H "Sec-Websocket-Version: 13" https://localhost:8453/health
+ HTTP/1.1 101 Switching Protocols
+ upgrade: websocket
+ sec-websocket-accept: eg18mwU7CDRGUF1Q+EJwPM335eM=
+ connection: upgrade
+
+ ?["up"]?["up"]?["up"]
+
+Since the stream handlers are effectively coroutines, you are free to keep state
+and yield using `cqueues library <http://www.25thandclement.com/~william/projects/cqueues.html>`_.
+
+This is especially useful for WebSockets, as you can stream content in a simple loop instead of
+chains of callbacks.
+
+Last thing you can publish from modules are *"snippets"*. Snippets are plain pieces of HTML code
+that are rendered at the end of the built-in webpage. The snippets can be extended with JS code to talk to already
+exported restful APIs and subscribe to WebSockets.
+
+.. code-block:: lua
+
+ http.snippets['/health'] = {'Health service', '<p>UP!</p>'}
+
+Custom RESTful services
+-----------------------
+
+A RESTful service is likely to respond differently to different type of methods and requests,
+there are three things that you can do in a service handler to send back results.
+First is to just send whatever you want to send back, it has to respect MIME type that the service
+declared in the endpoint definition. The response code would then be ``200 OK``, any non-string
+responses will be packed to JSON. Alternatively, you can respond with a number corresponding to
+the HTTP response code or send headers and body yourself.
+
+.. code-block:: lua
+
+ -- Our upvalue
+ local value = 42
+
+ -- Expose the service
+ local service = {'application/json',
+ function (h, stream)
+ -- Get request method and deal with it properly
+ local m = h:get(':method')
+ local path = h:get(':path')
+ log('method %s path %s', m, path)
+ -- Return table, response code will be '200 OK'
+ if m == 'GET' then
+ return {key = path, value = value}
+ -- Save body, perform check and either respond with 505 or 200 OK
+ elseif m == 'POST' then
+ local data = stream:get_body_as_string()
+ if not tonumber(data) then
+ return 500, 'Not a good request'
+ end
+ value = tonumber(data)
+ -- Unsupported method, return 405 Method not allowed
+ else
+ return 405, 'Cannot do that'
+ end
+ end}
+ modules.load('http')
+ http.config({
+ endpoints = { ['/service'] = service }
+ }, 'myservice')
+ -- do not forget to create socket of new kind using
+ -- net.listen(..., { kind = 'myservice' })
+ -- or configure systemd socket kresd-myservice.socket
+
+In some cases you might need to send back your own headers instead of default provided by HTTP handler,
+you can do this, but then you have to return ``false`` to notify handler that it shouldn't try to generate
+a response.
+
+.. code-block:: lua
+
+ local headers = require('http.headers')
+ function (h, stream)
+ -- Send back headers
+ local hsend = headers.new()
+ hsend:append(':status', '200')
+ hsend:append('content-type', 'binary/octet-stream')
+ assert(stream:write_headers(hsend, false))
+ -- Send back data
+ local data = 'binary-data'
+ assert(stream:write_chunk(data, true))
+ -- Disable default handler action
+ return false
+ end
+
diff --git a/modules/http/debug_opensslkeylog.c b/modules/http/debug_opensslkeylog.c
new file mode 100644
index 0000000..6709eb7
--- /dev/null
+++ b/modules/http/debug_opensslkeylog.c
@@ -0,0 +1,369 @@
+/*
+ * Dumps master keys for OpenSSL clients to file. The format is documented at
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
+ * Supports TLS 1.3 when used with OpenSSL 1.1.1.
+ *
+ * Copyright (C) 2014 Peter Wu <peter@lekensteyn.nl>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Usage:
+ * cc sslkeylog.c -shared -o libsslkeylog.so -fPIC -ldl
+ * SSLKEYLOGFILE=premaster.txt LD_PRELOAD=./libsslkeylog.so openssl ...
+ */
+
+/*
+ * A single libsslkeylog.so supports multiple OpenSSL runtime versions. If you
+ * would like to build this library without OpenSSL development headers and do
+ * not require support for older OpenSSL versions, then disable it by defining
+ * the NO_OPENSSL_102_SUPPORT or NO_OPENSSL_110_SUPPORT macros.
+ */
+/* Define to drop OpenSSL <= 1.0.2 support and require OpenSSL >= 1.1.0. */
+//#define NO_OPENSSL_102_SUPPORT
+/* Define to drop OpenSSL <= 1.1.0 support and require OpenSSL >= 1.1.1. */
+//#define NO_OPENSSL_110_SUPPORT
+
+/* No OpenSSL 1.1.0 support implies no OpenSSL 1.0.2 support. */
+#ifdef NO_OPENSSL_110_SUPPORT
+# define NO_OPENSSL_102_SUPPORT
+#endif
+
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE /* for RTLD_NEXT */
+#endif
+
+#include <dlfcn.h>
+#ifndef NO_OPENSSL_102_SUPPORT
+# include <openssl/ssl.h>
+#endif /* ! NO_OPENSSL_102_SUPPORT */
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#ifndef OPENSSL_SONAME
+/* fallback library if OpenSSL is not already loaded. Other values to try:
+ * libssl.so.0.9.8 libssl.so.1.0.0 libssl.so.1.1 */
+# define OPENSSL_SONAME "libssl.so"
+#endif
+
+/* When building for OpenSSL 1.1.0 or newer, no headers are required. */
+#ifdef NO_OPENSSL_102_SUPPORT
+typedef struct ssl_st SSL;
+typedef struct ssl_ctx_st SSL_CTX;
+/* Extra definitions for OpenSSL 1.1.0 support when headers are unavailable. */
+# ifndef NO_OPENSSL_110_SUPPORT
+typedef struct ssl_session_st SSL_SESSION;
+# define SSL3_RANDOM_SIZE 32
+# define SSL_MAX_MASTER_KEY_LENGTH 48
+# define OPENSSL_VERSION_NUMBER 0x10100000L
+# endif /* ! NO_OPENSSL_110_SUPPORT */
+#endif /* ! NO_OPENSSL_102_SUPPORT */
+
+static int keylog_file_fd = -1;
+
+/* Legacy routines for dumping TLS <= 1.2 secrets on older OpenSSL versions. */
+#ifndef NO_OPENSSL_110_SUPPORT
+#define PREFIX "CLIENT_RANDOM "
+#define PREFIX_LEN (sizeof(PREFIX) - 1)
+
+#pragma GCC diagnostic ignored "-Wpedantic"
+#pragma GCC diagnostic ignored "-Wunused-result"
+
+static inline void put_hex(char *buffer, int pos, char c)
+{
+ unsigned char c1 = ((unsigned char) c) >> 4;
+ unsigned char c2 = c & 0xF;
+ buffer[pos] = c1 < 10 ? '0' + c1 : 'A' + c1 - 10;
+ buffer[pos+1] = c2 < 10 ? '0' + c2 : 'A' + c2 - 10;
+}
+
+static void dump_to_fd(int fd, unsigned char *client_random,
+ unsigned char *master_key, int master_key_length)
+{
+ int pos, i;
+ char line[PREFIX_LEN + 2 * SSL3_RANDOM_SIZE + 1 +
+ 2 * SSL_MAX_MASTER_KEY_LENGTH + 1];
+
+ memcpy(line, PREFIX, PREFIX_LEN);
+ pos = PREFIX_LEN;
+ /* Client Random for SSLv3/TLS */
+ for (i = 0; i < SSL3_RANDOM_SIZE; i++) {
+ put_hex(line, pos, client_random[i]);
+ pos += 2;
+ }
+ line[pos++] = ' ';
+ /* Master Secret (size is at most SSL_MAX_MASTER_KEY_LENGTH) */
+ for (i = 0; i < master_key_length; i++) {
+ put_hex(line, pos, master_key[i]);
+ pos += 2;
+ }
+ line[pos++] = '\n';
+ /* Write at once rather than using buffered I/O. Perhaps there is concurrent
+ * write access so do not write hex values one by one. */
+ write(fd, line, pos);
+}
+#endif /* ! NO_OPENSSL_110_SUPPORT */
+
+static void init_keylog_file(void)
+{
+ if (keylog_file_fd >= 0)
+ return;
+
+ const char *filename = getenv("OPENSSLKEYLOGFILE");
+ if (filename) {
+ /* ctime output is max 26 bytes, POSIX 1003.1-2017 */
+ keylog_file_fd = open(filename, O_WRONLY | O_APPEND | O_CREAT, 0600);
+ if (keylog_file_fd >= 0) {
+ time_t timenow = time(NULL);
+ char txtnow[30] = { '#', ' ', 0 };
+ ctime_r(&timenow, txtnow + 2);
+ /* file is opened successfully and there is no data (pos == 0) */
+ write(keylog_file_fd, txtnow, strlen(txtnow));
+ }
+ }
+}
+
+static inline void *try_lookup_symbol(const char *sym, int optional)
+{
+ void *func = dlsym(RTLD_NEXT, sym);
+ if (!func && optional && dlsym(RTLD_NEXT, "SSL_new")) {
+ /* Symbol not found, but an old OpenSSL version was actually loaded. */
+ return NULL;
+ }
+ /* Symbol not found, OpenSSL is not loaded (linked) so try to load it
+ * manually. This is error-prone as it depends on a fixed library name.
+ * Perhaps it should be an env name? */
+ if (!func) {
+ void *handle = dlopen(OPENSSL_SONAME, RTLD_LAZY);
+ if (!handle) {
+ fprintf(stderr, "Lookup error for %s: %s\n", sym, dlerror());
+ abort();
+ }
+ func = dlsym(handle, sym);
+ if (!func && !optional) {
+ fprintf(stderr, "Cannot lookup %s\n", sym);
+ abort();
+ }
+ dlclose(handle);
+ }
+ return func;
+}
+
+static inline void *lookup_symbol(const char *sym)
+{
+ return try_lookup_symbol(sym, 0);
+}
+
+#ifndef NO_OPENSSL_110_SUPPORT
+typedef struct ssl_tap_state {
+ int master_key_length;
+ unsigned char master_key[SSL_MAX_MASTER_KEY_LENGTH];
+
+} ssl_tap_state_t;
+
+static inline SSL_SESSION *ssl_get_session(const SSL *ssl)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ static SSL_SESSION *(*func)();
+ if (!func) {
+ func = lookup_symbol("SSL_get_session");
+ }
+ return func(ssl);
+#else
+ return ssl->session;
+#endif
+}
+
+static void copy_master_secret(const SSL_SESSION *session,
+ unsigned char *master_key_out, int *keylen_out)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ static size_t (*func)();
+ if (!func) {
+ func = lookup_symbol("SSL_SESSION_get_master_key");
+ }
+ *keylen_out = func(session, master_key_out, SSL_MAX_MASTER_KEY_LENGTH);
+#else
+ if (session->master_key_length > 0) {
+ *keylen_out = session->master_key_length;
+ memcpy(master_key_out, session->master_key,
+ session->master_key_length);
+ }
+#endif
+}
+
+static void copy_client_random(const SSL *ssl, unsigned char *client_random)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ static size_t (*func)();
+ if (!func) {
+ func = lookup_symbol("SSL_get_client_random");
+ }
+ /* ssl->s3 is not checked in openssl 1.1.0-pre6, but let's assume that
+ * we have a valid SSL context if we have a non-NULL session. */
+ func(ssl, client_random, SSL3_RANDOM_SIZE);
+#else
+ if (ssl->s3) {
+ memcpy(client_random, ssl->s3->client_random, SSL3_RANDOM_SIZE);
+ }
+#endif
+}
+
+/* non-NULL if the new OpenSSL 1.1.1 keylog API is supported. */
+static int supports_keylog_api(void)
+{
+ static int supported = -1;
+ if (supported == -1) {
+ supported = try_lookup_symbol("SSL_CTX_set_keylog_callback", 1) != NULL;
+ }
+ return supported;
+}
+
+/* Copies SSL state for later comparison in tap_ssl_key. */
+static void ssl_tap_state_init(ssl_tap_state_t *state, const SSL *ssl)
+{
+ if (supports_keylog_api()) {
+ /* Favor using the callbacks API to extract secrets. */
+ return;
+ }
+
+ const SSL_SESSION *session = ssl_get_session(ssl);
+
+ memset(state, 0, sizeof(ssl_tap_state_t));
+ if (session) {
+ copy_master_secret(session, state->master_key, &state->master_key_length);
+ }
+}
+
+#define SSL_TAP_STATE(state, ssl) \
+ ssl_tap_state_t state; \
+ ssl_tap_state_init(&state, ssl)
+
+static void tap_ssl_key(const SSL *ssl, ssl_tap_state_t *state)
+{
+ if (supports_keylog_api()) {
+ /* Favor using the callbacks API to extract secrets. */
+ return;
+ }
+
+ const SSL_SESSION *session = ssl_get_session(ssl);
+ unsigned char client_random[SSL3_RANDOM_SIZE];
+ unsigned char master_key[SSL_MAX_MASTER_KEY_LENGTH];
+ int master_key_length = 0;
+
+ if (session) {
+ copy_master_secret(session, master_key, &master_key_length);
+ /* Assume we have a client random if the master key is set. */
+ if (master_key_length > 0) {
+ copy_client_random(ssl, client_random);
+ }
+ }
+
+ /* Write the logfile when the master key is available for SSLv3/TLSv1. */
+ if (master_key_length > 0) {
+ /* Skip writing keys if it did not change. */
+ if (state->master_key_length == master_key_length &&
+ memcmp(state->master_key, master_key, master_key_length) == 0) {
+ return;
+ }
+
+ init_keylog_file();
+ if (keylog_file_fd >= 0) {
+ dump_to_fd(keylog_file_fd, client_random, master_key,
+ master_key_length);
+ }
+ }
+}
+
+int SSL_connect(SSL *ssl)
+{
+ static int (*func)();
+ if (!func) {
+ func = lookup_symbol(__func__);
+ }
+ SSL_TAP_STATE(state, ssl);
+ int ret = func(ssl);
+ tap_ssl_key(ssl, &state);
+ return ret;
+}
+
+int SSL_do_handshake(SSL *ssl)
+{
+ static int (*func)();
+ if (!func) {
+ func = lookup_symbol(__func__);
+ }
+ SSL_TAP_STATE(state, ssl);
+ int ret = func(ssl);
+ tap_ssl_key(ssl, &state);
+ return ret;
+}
+
+int SSL_accept(SSL *ssl)
+{
+ static int (*func)();
+ if (!func) {
+ func = lookup_symbol(__func__);
+ }
+ SSL_TAP_STATE(state, ssl);
+ int ret = func(ssl);
+ tap_ssl_key(ssl, &state);
+ return ret;
+}
+
+int SSL_read(SSL *ssl, void *buf, int num)
+{
+ static int (*func)();
+ if (!func) {
+ func = lookup_symbol(__func__);
+ }
+ SSL_TAP_STATE(state, ssl);
+ int ret = func(ssl, buf, num);
+ tap_ssl_key(ssl, &state);
+ return ret;
+}
+
+int SSL_write(SSL *ssl, const void *buf, int num)
+{
+ static int (*func)();
+ if (!func) {
+ func = lookup_symbol(__func__);
+ }
+ SSL_TAP_STATE(state, ssl);
+ int ret = func(ssl, buf, num);
+ tap_ssl_key(ssl, &state);
+ return ret;
+}
+#endif /* ! NO_OPENSSL_110_SUPPORT */
+
+/* Key extraction via the new OpenSSL 1.1.1 API. */
+static void keylog_callback(const SSL *ssl, const char *line)
+{
+ init_keylog_file();
+ if (keylog_file_fd >= 0) {
+ write(keylog_file_fd, line, strlen(line));
+ write(keylog_file_fd, "\n", 1);
+ }
+}
+
+SSL *SSL_new(SSL_CTX *ctx)
+{
+ static SSL *(*func)();
+ static void (*set_keylog_cb)();
+ if (!func) {
+ func = lookup_symbol(__func__);
+#ifdef NO_OPENSSL_110_SUPPORT
+ /* The new API MUST be available since OpenSSL 1.1.1. */
+ set_keylog_cb = lookup_symbol("SSL_CTX_set_keylog_callback");
+#else /* ! NO_OPENSSL_110_SUPPORT */
+ /* May be NULL if used with an older OpenSSL runtime library. */
+ set_keylog_cb = try_lookup_symbol("SSL_CTX_set_keylog_callback", 1);
+#endif /* ! NO_OPENSSL_110_SUPPORT */
+ }
+ if (set_keylog_cb) {
+ /* Override any previous key log callback. */
+ set_keylog_cb(ctx, keylog_callback);
+ }
+ return func(ctx);
+}
diff --git a/modules/http/http.lua.in b/modules/http/http.lua.in
new file mode 100644
index 0000000..b6cf16a
--- /dev/null
+++ b/modules/http/http.lua.in
@@ -0,0 +1,418 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+
+-- Load dependent modules
+if not stats then modules.load('stats') end
+if not bogus_log then modules.load('bogus_log') end
+
+local ffi = require('ffi')
+local cqueues = require('cqueues')
+cqueues.socket = require('cqueues.socket')
+assert(cqueues.VERSION >= 20150112) -- fdopen changed semantics
+
+-- This is a module that does the heavy lifting to provide an HTTP/2 enabled
+-- server that supports TLS by default and provides endpoint for other modules
+-- in order to enable them 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.
+local http_server = require('http.server')
+local http_headers = require('http.headers')
+local http_websocket = require('http.websocket')
+local http_util = require "http.util"
+local has_mmdb, mmdb = pcall(require, 'mmdb')
+
+-- A sub-module for certificate management.
+local tls_cert = require('kres_modules.http_tls_cert')
+
+-- Module declaration
+local M = {
+ servers = {},
+ configs = { _builtin = {} } -- configuration templates
+}
+
+-- inherited by all configurations
+M.configs._builtin._all = {
+ cq = worker.bg_worker.cq,
+ cert = 'self.crt',
+ key = 'self.key',
+ ephemeral = true,
+ client_timeout = 5
+}
+-- log errors but do not throw
+M.configs._builtin._all.onerror = function(myserver, context, op, err, errno) -- luacheck: ignore 212
+ local msg = op .. ' on ' .. tostring(context) .. ' failed'
+ if err then
+ msg = msg .. ': ' .. tostring(err)
+ end
+ log_info(ffi.C.LOG_GRP_HTTP, msg)
+end
+
+-- M.config() without explicit "kind" modifies this
+M.configs._all = {}
+
+-- DoH
+M.configs._builtin.doh_legacy = {}
+
+-- management endpoint
+M.configs._builtin.webmgmt = {}
+
+-- Map extensions to MIME type
+local mime_types = {
+ js = 'application/javascript',
+ css = 'text/css',
+ tpl = 'text/html',
+ ico = 'image/x-icon'
+}
+
+-- Preload static contents, nothing on runtime will touch the disk
+local function pgload(relpath, modname)
+ if not modname then modname = 'http' end
+ local fp, err = io.open(string.format(
+ '@modules_dir@/%s/%s', modname, relpath), 'r')
+ if not fp then
+ fp, err = io.open(string.format(
+ '@modules_dir@/%s/static/%s', modname, relpath), 'r')
+ end
+ if not fp then error(err) end
+ local data = fp:read('*all')
+ fp:close()
+ -- Guess content type
+ local ext = relpath:match('[^\\.]+$')
+ return {mime_types[ext] or 'text', data, nil, 86400}
+end
+M.page = pgload
+
+-- Preloaded static assets
+local pages = {
+ 'favicon.ico',
+ 'kresd.js',
+ 'kresd.css',
+ 'jquery.js',
+ 'd3.js',
+ 'topojson.js',
+ 'datamaps.world.min.js',
+ 'dygraph.min.js',
+ 'selectize.min.js',
+ 'selectize.bootstrap3.css',
+ 'bootstrap.min.js',
+ 'bootstrap.min.css',
+ 'bootstrap-theme.min.css',
+ 'glyphicons-halflings-regular.woff2',
+}
+
+-- Serve preloaded root page
+local function serve_root()
+ local data = pgload('main.tpl')[2]
+ data = data
+ :gsub('{{ title }}', M.title or ('kresd @ ' .. hostname()))
+ :gsub('{{ host }}', hostname())
+ return function (_, _)
+ -- Render snippets
+ local rsnippets = {}
+ for _,v in pairs(M.snippets) do
+ local sid = string.lower(string.gsub(v[1], ' ', '-'))
+ table.insert(rsnippets, string.format('<section id="%s"><h2>%s</h2>\n%s</section>', sid, v[1], v[2]))
+ end
+ -- Return index page
+ return data
+ :gsub('{{ snippets }}', table.concat(rsnippets, '\n'))
+ end
+end
+
+-- Export HTTP service endpoints
+M.configs._builtin.doh_legacy.endpoints = {}
+M.configs._builtin.webmgmt.endpoints = {}
+local mgmt_endpoints = M.configs._builtin.webmgmt.endpoints
+
+mgmt_endpoints['/'] = {'text/html', serve_root()}
+
+-- Export static pages
+for _, pg in ipairs(pages) do
+ mgmt_endpoints['/'..pg] = pgload(pg)
+end
+
+-- Export built-in prometheus interface
+local prometheus = require('kres_modules.prometheus')
+for k, v in pairs(prometheus.endpoints) do
+ mgmt_endpoints[k] = v
+end
+M.prometheus = prometheus
+
+-- Export built-in trace interface
+local http_trace = require('kres_modules.http_trace')
+for k, v in pairs(http_trace.endpoints) do
+ mgmt_endpoints[k] = v
+end
+M.trace = http_trace
+
+M.configs._builtin.doh_legacy.endpoints = {}
+local http_doh = require('kres_modules.http_doh')
+for k, v in pairs(http_doh.endpoints) do
+ mgmt_endpoints[k] = v
+ M.configs._builtin.doh_legacy.endpoints[k] = v
+end
+M.doh = http_doh
+
+-- Export HTTP service page snippets
+M.snippets = {}
+
+-- Serve known requests, for methods other than GET
+-- the endpoint must be a closure and not a preloaded string
+local function serve(endpoints, h, stream)
+ local hsend = http_headers.new()
+ local path = h:get(':path')
+ local entry = endpoints[path]
+ if not entry then -- Accept top-level path match
+ entry = endpoints[path:match '^/[^/?]*']
+ end
+ -- Unpack MIME and data
+ local data, mime, ttl, any_origin, err
+ if entry then
+ mime = entry[1]
+ data = entry[2]
+ ttl = entry[4]
+ any_origin = entry[5]
+ end
+ -- Get string data out of service endpoint
+ if type(data) == 'function' then
+ local set_mime, set_ttl
+ data, err, set_mime, set_ttl = data(h, stream)
+ -- Override default endpoint mime/TTL
+ if set_mime then mime = set_mime end
+ if set_ttl then ttl = set_ttl end
+ -- Handler doesn't provide any data
+ if data == false then return end
+ if type(data) == 'number' then return tostring(data), err end
+ -- Methods other than GET require handler to be closure
+ elseif h:get(':method') ~= 'GET' then
+ return '501', ''
+ end
+ if type(data) == 'table' then data = tojson(data) end
+ if not mime or type(data) ~= 'string' then
+ return '404', ''
+ else
+ -- Serve content type appropriately
+ hsend:append(':status', '200')
+ hsend:append('content-type', mime)
+ hsend:append('content-length', tostring(#data))
+ if ttl then
+ hsend:append('cache-control', string.format('max-age=%d', ttl))
+ end
+ if any_origin then
+ hsend:append('access-control-allow-origin', '*')
+ end
+ assert(stream:write_headers(hsend, false))
+ assert(stream:write_chunk(data, true))
+ end
+end
+
+-- Web server service closure
+local function route(endpoints)
+ assert(type(endpoints) == 'table', 'endpoints are not a table, is it a botched template?')
+ return function (_, stream)
+ -- HTTP/2: We're only permitted to send in open/half-closed (remote)
+ local connection = stream.connection
+ if connection.version >= 2 then
+ if stream.state ~= 'open' and stream.state ~= 'half closed (remote)' then
+ return
+ end
+ end
+ -- Start reading headers
+ local h = assert(stream:get_headers())
+ local m = h:get(':method')
+ local path = h:get(':path')
+
+ -- Upgrade connection to WebSocket
+ local ws = http_websocket.new_from_stream(stream, h)
+ if ws then
+ log_info(ffi.C.LOG_GRP_HTTP, '%s %s HTTP/%s web socket open',
+ m, path, tostring(connection.version))
+ assert(ws:accept { protocols = {'json'} })
+ -- Continue streaming results to client
+ local ep = endpoints[path]
+ local cb = ep[3]
+ if cb then
+ cb(h, ws)
+ end
+ ws:close()
+ log_info(ffi.C.LOG_GRP_HTTP, '%s %s HTTP/%s web socket closed',
+ m, path, tostring(connection.version))
+ return
+ else
+ local ok, err, reason = http_util.yieldable_pcall(serve, endpoints, h, stream)
+ if not ok or err then
+ err = err or '500'
+ log_info(ffi.C.LOG_GRP_HTTP, '%s %s HTTP/%s %s %s',
+ m, path, tostring(connection.version), err, reason or '')
+ -- Method is not supported
+ local hsend = http_headers.new()
+ hsend:append(':status', err)
+ if reason then
+ assert(stream:write_headers(hsend, false))
+ assert(stream:write_chunk(reason, true))
+ else
+ assert(stream:write_headers(hsend, true))
+ end
+ else
+ log_info(ffi.C.LOG_GRP_HTTP, '%s %s HTTP/%s 200',
+ m, path, tostring(connection.version))
+ end
+
+ end
+ end
+end
+
+-- @function Merge dictionaries, nil is like empty dict.
+-- Values from right-hand side dictionaries take precedence.
+local function mergeconf(...)
+ local merged = {}
+ local ntables = select('#', ...)
+ local tables = {...}
+ for i = 1, ntables do
+ local intable = tables[i]
+ if intable ~= nil then
+ assert(type(intable) == 'table', 'cannot merge non-tables')
+ for key, val in pairs(intable) do
+ merged[key] = val
+ end
+ end
+ end
+ return merged
+end
+
+-- @function Listen on given socket
+-- using configuration for specific "kind" of HTTP server
+local function add_socket(fd, kind, addr_str)
+ assert(M.servers[fd] == nil, 'socket is already served by an HTTP instance')
+ local conf = mergeconf(M.configs._builtin._all, M.configs._builtin[kind],
+ M.configs._all, M.configs[kind])
+ conf.socket = cqueues.socket.fdopen({ fd = fd, reuseport = true, reuseaddr = true })
+ if conf.tls ~= false then -- Create a TLS context, either from files or new.
+ if conf.ephemeral then
+ if not M.ephem_state then
+ M.ephem_state = { servers = M.servers }
+ tls_cert.ephemeral_state_maintain(M.ephem_state, conf.cert, conf.key)
+ end
+ conf.ctx = M.ephem_state.ctx
+ else
+ local certs, key = tls_cert.load(conf.cert, conf.key)
+ conf.ctx = tls_cert.new_tls_context(certs, key)
+ end
+ assert(conf.ctx)
+ end
+ -- Compose server handler
+ local routes = route(conf.endpoints)
+ conf.onstream = routes
+ -- Create TLS context and start listening
+ local s, err = http_server.new(conf)
+ -- Manually call :listen() so that we are bound before calling :localname()
+ if s then
+ err = select(2, s:listen())
+ end
+ if err then
+ panic('failed to listen on %s: %s', addr_str, err)
+ end
+ M.servers[fd] = {kind = kind, server = s, config = conf}
+end
+
+-- @function Stop listening on given socket
+local function remove_socket(fd)
+ local instance = M.servers[fd]
+ assert(instance, 'HTTP module is not listening on given socket')
+
+ instance.server:close()
+ M.servers[fd] = nil
+end
+
+-- @function Listen for config changes from net.listen()/net.close()
+local function cb_socket(...)
+ local added, endpoint, addr_str = unpack({...})
+ endpoint = ffi.cast('struct endpoint **', endpoint)[0]
+ local kind = ffi.string(endpoint.flags.kind)
+ local socket = endpoint.fd
+ if added then
+ return add_socket(socket, kind, addr_str)
+ else
+ return remove_socket(socket)
+ end
+end
+
+-- @function Init module
+function M.init()
+ net.register_endpoint_kind('doh_legacy', cb_socket)
+ net.register_endpoint_kind('webmgmt', cb_socket)
+end
+
+-- @function Cleanup module
+function M.deinit()
+ for fd, _ in pairs(M.servers) do
+ remove_socket(fd)
+ end
+ tls_cert.ephemeral_state_destroy(M.ephem_state)
+ net.register_endpoint_kind('doh_legacy')
+ net.register_endpoint_kind('webmgmt')
+end
+
+-- @function Configure module, i.e. store new configuration template
+-- kind = socket type (doh_legacy/webmgmt)
+function M.config(conf, kind)
+ if conf == nil and kind == nil then
+ -- default module config, nothing to do
+ return
+ end
+
+ kind = kind or '_all'
+ assert(type(kind) == 'string')
+
+ local operation
+ -- builtins cannot be removed or added
+ if M.configs._builtin[kind] then
+ operation = 'modify'
+ conf = conf or {}
+ elseif M.configs[kind] then -- config on an existing user template
+ if conf then operation = 'modify'
+ else operation = 'delete' end
+ else -- config for not-yet-existing template
+ if conf then operation = 'add'
+ else panic('[http] endpoint kind "%s" does not exist, '
+ .. 'nothing to delete', kind) end
+ end
+
+ if operation == 'modify' or operation == 'add' then
+ assert(type(conf) == 'table', 'config { cert = "...", key = "..." }')
+
+ if conf.cert then
+ conf.ephemeral = false
+ if not conf.key then
+ panic('[http] certificate provided, but missing key')
+ end
+ -- test if it can be loaded or not
+ tls_cert.load(conf.cert, conf.key)
+ end
+ if conf.geoip then
+ if has_mmdb then
+ M.geoip = mmdb.open(conf.geoip)
+ else
+ error('[http] mmdblua library not found, please remove GeoIP configuration')
+ end
+ end
+ end
+
+ for _, instance in pairs(M.servers) do
+ -- modification cannot be implemented as
+ -- remove_socket + add_socket because remove closes the socket
+ if instance.kind == kind or kind == '_all' then
+ panic('unable to modify configuration for '
+ .. 'endpoint kind "%s" because it is in '
+ .. 'use, use net.close() first', kind)
+ end
+ end
+
+ if operation == 'add' then
+ net.register_endpoint_kind(kind, cb_socket)
+ elseif operation == 'delete' then
+ net.register_endpoint_kind(kind)
+ end
+ M.configs[kind] = conf
+end
+
+return M
diff --git a/modules/http/http.test.lua b/modules/http/http.test.lua
new file mode 100644
index 0000000..b882f10
--- /dev/null
+++ b/modules/http/http.test.lua
@@ -0,0 +1,128 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- check prerequisites
+local has_http = pcall(require, 'kres_modules.http') and pcall(require, 'http.request')
+if not has_http then
+ -- 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')
+ local endpoints = http.configs._builtin.webmgmt.endpoints
+
+ -- custom endpoints
+ endpoints['/test'] = {'text/custom', function () return 'hello' end}
+
+ -- setup HTTP module with an additional endpoint
+ http.config({
+ tls = false,
+ endpoints = endpoints,
+ }, 'webtest')
+
+ local bound
+ for _ = 1,1000 do
+ bound, _err = pcall(net.listen, '127.0.0.1', math.random(20000, 29999), { kind = 'webtest'})
+ if bound then
+ break
+ end
+ end
+ assert(bound, 'unable to bind a port for HTTP module (1000 attempts)')
+
+ -- globals for this module
+ local _, host, port
+ local function start_server()
+ local server_fd = next(http.servers)
+ assert(server_fd)
+ local server = http.servers[server_fd].server
+ ok(server ~= nil, 'creates server instance')
+ _, host, port = server:localname()
+ ok(host and port, 'binds to an interface')
+ end
+
+ -- helper for returning useful values to test on
+ local function http_get(uri)
+ local headers, stream = assert(request.new_from_uri(uri .. '/'):go(16))
+ local body = assert(stream:get_body_as_string())
+ return tonumber(headers:get(':status')), body, headers:get('content-type')
+ end
+
+ -- test whether http interface responds and binds
+ local function test_builtin_pages()
+ local code, body, mime
+ local uri = string.format('http://%s:%d', host, port)
+ -- simple static page
+ code, body, mime = http_get(uri .. '/')
+ same(code, 200, 'static page return 200 OK')
+ ok(#body > 0, 'static page has non-empty body')
+ same(mime, 'text/html', 'static page has text/html content type')
+ -- custom endpoint
+ code, body, mime = http_get(uri .. '/test')
+ same(code, 200, 'custom page return 200 OK')
+ same(body, 'hello', 'custom page has non-empty body')
+ same(mime, 'text/custom', 'custom page has custom content type')
+ -- non-existent page
+ code = http_get(uri .. '/badpage')
+ same(code, 404, 'non-existent page returns 404')
+ -- /stats endpoint serves metrics
+ code, body, mime = http_get(uri .. '/stats')
+ same(code, 200, '/stats page return 200 OK')
+ ok(#body > 0, '/stats page has non-empty body')
+ same(mime, 'application/json', '/stats page has correct content type')
+ -- /metrics serves metrics
+ code, body, mime = http_get(uri .. '/metrics')
+ same(code, 200, '/metrics page return 200 OK')
+ ok(#body > 0, '/metrics page has non-empty body')
+ same(mime, 'text/plain; version=0.0.4', '/metrics page has correct content type')
+ -- /metrics serves frequent
+ code, body, mime = http_get(uri .. '/frequent')
+ same(code, 200, '/frequent page return 200 OK')
+ ok(#body > 0, '/frequent page has non-empty body')
+ same(mime, 'application/json', '/frequent page has correct content type')
+ -- /metrics serves bogus
+ code, body, mime = http_get(uri .. '/bogus')
+ same(code, 200, '/bogus page return 200 OK')
+ ok(#body > 0, '/bogus page has non-empty body')
+ same(mime, 'application/json', '/bogus page has correct content type')
+ -- /trace serves trace log for requests
+ code, body, mime = http_get(uri .. '/trace/localhost/A')
+ same(code, 200, '/trace page return 200 OK')
+ ok(#body > 0, '/trace page has non-empty body')
+ same(mime, 'text/plain', '/trace page has correct content type')
+ -- /trace checks variables
+ code = http_get(uri .. '/trace/localhost/BADTYPE')
+ same(code, 400, '/trace checks type')
+ code = http_get(uri .. '/trace/')
+ same(code, 400, '/trace requires name')
+ end
+
+ -- AF_UNIX tests (very basic ATM)
+ local function test_unix_socket()
+ local s_path = os.tmpname()
+ os.remove(s_path) -- on POSIX .tmpname() (usually) creates a file :-/
+ ok(net.listen(s_path, nil, { kind = 'webmgmt' }), 'AF_UNIX net.listen() on ' .. s_path)
+ -- Unfortunately we can't use standard functions for fetching http://
+ local socket = require("cqueues.socket")
+ local sock = socket.connect({ path = s_path })
+ local connection = require('http.h2_connection')
+ local conn = connection.new(sock, 'client')
+ local _, err = conn:connect()
+ os.remove(s_path) -- don't leave garbage around, hopefully not even on errors
+ same(err, nil, 'AF_UNIX connect(): ' .. (err or 'OK'))
+ same(conn:ping(), true, 'AF_UNIX http ping')
+ -- here we might do `conn:new_stream()` and some real queries
+ same(conn:close(), true, 'AF_UNIX close')
+ end
+
+ -- plan tests
+ local tests = {
+ start_server,
+ test_builtin_pages,
+ test_unix_socket,
+ }
+
+ return tests
+end
diff --git a/modules/http/http_doh.lua b/modules/http/http_doh.lua
new file mode 100644
index 0000000..33815f7
--- /dev/null
+++ b/modules/http/http_doh.lua
@@ -0,0 +1,116 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local basexx = require('basexx')
+local ffi = require('ffi')
+local condition = require('cqueues.condition')
+
+-- Trace execution of DNS queries
+local function serve_doh(h, stream)
+ local input
+ local method = h:get(':method')
+ 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'), '^/[^?]*%?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
+ if #input_b64 > 1368 then -- base64url encode 1024
+ return 414, 'query parameter in URI too long'
+ end
+ input = basexx.from_url64(input_b64)
+ if not input then
+ return 400, 'invalid base64url'
+ end
+ else
+ return 405, 'only HTTP POST and GET are supported'
+ end
+
+ if not input or #input < 12 then
+ return 400, 'input too short'
+ elseif #input > 1024 then
+ return 413, 'input too long'
+ end
+
+ local content_type = h:get('content-type') or 'application/dns-message'
+ if content_type ~= 'application/dns-message' then
+ return 415, 'only Content-Type: application/dns-message is supported'
+ end
+-- RFC 8484 section-4.1 allows us to ignore Accept header
+-- local accept = h:get('accept') or 'application/dns-message'
+-- if accept ~= 'application/dns-message' then
+-- return 406, 'only Accept: application/dns-message is supported'
+-- end
+
+ -- We get these values beforehand, because it's easier to handle errors now.
+ local _, peer_addr, peer_port = stream:peername()
+ local _, dst_addr, dst_port = stream:localname()
+ if not (peer_addr and peer_port and dst_addr and dst_port) then
+ -- The connection probably died in the meantime or something.
+ return 504, 'failed to determine your address'
+ end
+
+ -- Output buffer
+ local output
+ local output_ttl
+
+ -- Wait for the result of the query
+ -- Note: We can't do non-blocking write to stream directly from resolve callbacks
+ -- because they don't run inside cqueue.
+ local cond = condition.new()
+ local waiting, done = false, false
+ local finish_cb = function (answer, _)
+ output_ttl = ffi.C.packet_ttl(answer)
+ -- binary output
+ output = ffi.string(answer.wire, answer.size)
+ if waiting then
+ cond:signal()
+ end
+ done = true
+ end
+
+ -- convert query to knot_pkt_t
+ local wire = ffi.cast("void *", input)
+ local pkt = ffi.gc(ffi.C.knot_pkt_new(wire, #input, nil), ffi.C.knot_pkt_free)
+ if not pkt then
+ return 500, 'internal server error'
+ end
+
+ local result = ffi.C.knot_pkt_parse(pkt, 0)
+ if result ~= 0 then
+ return 400, 'unparseable DNS message'
+ end
+
+ -- set source address so filters can work
+ local function init_cb(req)
+ req.qsource.addr = ffi.C.kr_straddr_socket(peer_addr, peer_port, req.pool)
+ req.qsource.dst_addr = ffi.C.kr_straddr_socket(dst_addr, dst_port, req.pool)
+ assert(req.qsource.addr ~= nil and req.qsource.dst_addr ~= nil)
+ req.qsource.flags.tcp = true
+ req.qsource.flags.tls = (stream.connection:checktls() ~= nil)
+ req.qsource.flags.http = true
+ end
+
+ -- resolve query
+ worker.resolve_pkt(pkt, {}, finish_cb, init_cb)
+ if not done then
+ waiting = true
+ cond:wait()
+ end
+
+ -- Return buffered data
+ if not done then
+ return 504, 'huh?' -- FIXME
+ end
+ return output, nil, 'application/dns-message', output_ttl
+end
+
+-- Export endpoints
+return {
+ endpoints = {
+ ['/doh'] = {'text/plain', serve_doh, nil, nil, true},
+ ['/dns-query'] = {'text/plain', serve_doh, nil, nil, true},
+ }
+}
diff --git a/modules/http/http_doh.test.lua b/modules/http/http_doh.test.lua
new file mode 100644
index 0000000..f0685cb
--- /dev/null
+++ b/modules/http/http_doh.test.lua
@@ -0,0 +1,419 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local basexx = require('basexx')
+local ffi = require('ffi')
+
+local function gen_huge_answer(_, req)
+ local answer = req:ensure_answer()
+ ffi.C.kr_pkt_make_auth_header(answer)
+
+ answer:rcode(kres.rcode.NOERROR)
+
+ -- 64k answer
+ answer:begin(kres.section.ANSWER)
+ answer:put('\4test\0', 300, answer:qclass(), kres.type.URI,
+ '\0\0\0\0' .. string.rep('0', 65000))
+ answer:put('\4test\0', 300, answer:qclass(), kres.type.URI,
+ '\0\0\0\0' .. 'done')
+ return kres.DONE
+end
+
+local function gen_varying_ttls(_, req)
+ local qry = req:current()
+ local answer = req:ensure_answer()
+ ffi.C.kr_pkt_make_auth_header(answer)
+
+ answer:rcode(kres.rcode.NOERROR)
+
+ -- varying TTLs in ANSWER section
+ answer:begin(kres.section.ANSWER)
+ answer:put(qry.sname, 1800, answer:qclass(), kres.type.AAAA,
+ '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1')
+ answer:put(qry.sname, 900, answer:qclass(), kres.type.A, '\127\0\0\1')
+ answer:put(qry.sname, 20000, answer:qclass(), kres.type.NS, '\2ns\4test\0')
+
+ -- shorter TTL than all other RRs
+ answer:begin(kres.section.AUTHORITY)
+ answer:put('\4test\0', 300, answer:qclass(), kres.type.SOA,
+ '\2ns\4test\0\6nobody\7invalid\0\0\0\0\1\0\0\14\16\0\0\4\176\0\9\58\128\0\0\42\48')
+ return kres.DONE
+end
+
+function parse_pkt(input, desc)
+ local wire = ffi.cast("void *", input)
+ local pkt = ffi.C.knot_pkt_new(wire, #input, nil);
+ assert(pkt, desc .. ': failed to create new packet')
+
+ local result = ffi.C.knot_pkt_parse(pkt, 0)
+ ok(result == 0, desc .. ': knot_pkt_parse works on received answer')
+ return pkt
+end
+
+local function check_ok(req, desc)
+ local headers, stream, errno = req:go(16)
+ if errno then
+ local errmsg = stream
+ nok(errmsg, desc .. ': ' .. errmsg)
+ return
+ end
+ same(tonumber(headers:get(':status')), 200, desc .. ': status 200')
+ same(headers:get('content-type'), 'application/dns-message', desc .. ': content-type')
+ local body = assert(stream:get_body_as_string())
+ local pkt = parse_pkt(body, desc)
+ return headers, pkt
+end
+
+local function check_err(req, exp_status, desc)
+ local headers, errmsg, errno = req:go(16)
+ if errno then
+ nok(errmsg, desc .. ': ' .. errmsg)
+ return
+ end
+ local got_status = headers:get(':status')
+ same(got_status, exp_status, desc)
+end
+
+-- check prerequisites
+local has_http = pcall(require, 'kres_modules.http') and pcall(require, 'http.request')
+if not has_http then
+ -- 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.'})))
+ policy.add(policy.suffix(gen_varying_ttls, policy.todnames({'noerror.test.'})))
+
+ modules.load('http')
+ http.config({
+ tls = false,
+ }, 'doh_legacy')
+
+ local bound
+ for _ = 1,1000 do
+ bound, _err = pcall(net.listen, '127.0.0.1', math.random(30000, 39999), { kind = 'doh_legacy' })
+ if bound then
+ break
+ end
+ end
+ assert(bound, 'unable to bind a port for HTTP module (1000 attempts)')
+
+ local _, host, port, req_templ, uri_templ
+ local function start_server()
+ local request = require('http.request')
+ local server_fd = next(http.servers)
+ assert(server_fd)
+ local server = http.servers[server_fd].server
+ ok(server ~= nil, 'creates server instance')
+ _, host, port = server:localname()
+ ok(host and port, 'binds to an interface')
+ uri_templ = string.format('http://%s:%d/doh', host, port)
+ req_templ = assert(request.new_from_uri(uri_templ))
+ req_templ.headers:upsert('content-type', 'application/dns-message')
+ end
+
+
+ -- test a valid DNS query using POST
+ local function test_post_servfail()
+ local desc = 'valid POST query which ends with SERVFAIL'
+ local req = req_templ:clone()
+ 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
+
+ local function test_post_noerror()
+ local desc = 'valid POST query which ends with NOERROR'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'POST')
+ req:set_body(basexx.from_base64( -- noerror.test. A
+ 'vMEBAAABAAAAAAAAB25vZXJyb3IEdGVzdAAAAQAB'))
+ local headers, pkt = check_ok(req, desc)
+ if not (headers and pkt) then
+ return
+ end
+ -- HTTP TTL is minimum from all RRs in the answer
+ same(headers:get('cache-control'), 'max-age=300', desc .. ': TTL 900')
+ same(pkt:rcode(), kres.rcode.NOERROR, desc .. ': rcode matches')
+ same(pkt:ancount(), 3, desc .. ': ANSWER is present')
+ same(pkt:nscount(), 1, desc .. ': AUTHORITY is present')
+ same(pkt:arcount(), 0, desc .. ': ADDITIONAL is empty')
+ end
+
+ local function test_post_nxdomain()
+ local desc = 'valid POST query which ends with NXDOMAIN'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'POST')
+ req:set_body(basexx.from_base64( -- nxdomain.test. A
+ 'viABAAABAAAAAAAACG54ZG9tYWluBHRlc3QAAAEAAQ=='))
+ local headers, pkt = check_ok(req, desc)
+ if not (headers and pkt) then
+ return
+ end
+ same(headers:get('cache-control'), 'max-age=10800', desc .. ': TTL 10800')
+ same(pkt:rcode(), kres.rcode.NXDOMAIN, desc .. ': rcode matches')
+ same(pkt:nscount(), 1, desc .. ': AUTHORITY is present')
+ end
+
+ -- RFC 8484 section 6 explicitly allows huge answers over HTTP
+ local function test_huge_answer()
+ policy.add(policy.suffix(gen_huge_answer, policy.todnames({'huge.test'})))
+ local desc = 'POST query for a huge answer'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'POST')
+ req:set_body(basexx.from_base64( -- huge.test. URI, no EDNS
+ 'HHwBAAABAAAAAAAABGh1Z2UEdGVzdAABAAAB'))
+ local _, pkt = check_ok(req, desc)
+ same(pkt:rcode(), kres.rcode.NOERROR, desc .. ': rcode NOERROR')
+ same(pkt:tc(), false, desc .. ': no TC bit')
+ same(pkt:ancount(), 2, desc .. ': ANSWER contains both RRs')
+ end
+
+ -- test an invalid DNS query using POST
+ local function test_post_short_input()
+ local req = assert(req_templ:clone())
+ req.headers:upsert(':method', 'POST')
+ req:set_body(string.rep('0', 11)) -- 11 bytes < DNS msg header
+ check_err(req, '400', 'too short POST finishes with 400')
+ end
+
+ local function test_post_long_input()
+ local req = assert(req_templ:clone())
+ req.headers:upsert(':method', 'POST')
+ req:set_body(string.rep('s', 1025)) -- > DNS msg over UDP
+ check_err(req, '413', 'too long POST finishes with 413')
+ end
+
+ local function test_post_unparseable_input()
+ local req = assert(req_templ:clone())
+ req.headers:upsert(':method', 'POST')
+ req:set_body(string.rep('\0', 1024)) -- garbage
+ check_err(req, '400', 'unparseable DNS message finishes with 400')
+ end
+
+ local function test_post_unsupp_type()
+ local req = assert(req_templ:clone())
+ req.headers:upsert(':method', 'POST')
+ req.headers:upsert('content-type', 'application/dns+json')
+ req:set_body(string.rep('\0', 12)) -- valid message
+ check_err(req, '415', 'unsupported request content type finishes with 415')
+ end
+
+ -- test a valid DNS query using GET
+ local function test_get_servfail()
+ local desc = 'valid GET query which ends with SERVFAIL'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path', '/doh?dns=' -- 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
+
+ local function test_get_noerror()
+ local desc = 'valid GET query which ends with NOERROR'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path', '/doh?dns=' -- noerror.test. A
+ .. 'vMEBAAABAAAAAAAAB25vZXJyb3IEdGVzdAAAAQAB')
+ local headers, pkt = check_ok(req, desc)
+ if not (headers and pkt) then
+ return
+ end
+ -- HTTP TTL is minimum from all RRs in the answer
+ same(headers:get('cache-control'), 'max-age=300', desc .. ': TTL 900')
+ same(pkt:rcode(), kres.rcode.NOERROR, desc .. ': rcode matches')
+ same(pkt:ancount(), 3, desc .. ': ANSWER is present')
+ same(pkt:nscount(), 1, desc .. ': AUTHORITY is present')
+ same(pkt:arcount(), 0, desc .. ': ADDITIONAL is empty')
+ end
+
+ local function test_get_nxdomain()
+ local desc = 'valid GET query which ends with NXDOMAIN'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path', '/doh?dns=' -- nxdomain.test. A
+ .. 'viABAAABAAAAAAAACG54ZG9tYWluBHRlc3QAAAEAAQ')
+ local headers, pkt = check_ok(req, desc)
+ if not (headers and pkt) then
+ return
+ end
+ same(headers:get('cache-control'), 'max-age=10800', desc .. ': TTL 10800')
+ same(pkt:rcode(), kres.rcode.NXDOMAIN, desc .. ': rcode matches')
+ same(pkt:nscount(), 1, desc .. ': AUTHORITY is present')
+ end
+
+ local function test_get_other_params_before_dns()
+ local desc = 'GET query with other parameters before dns is valid'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path',
+ '/doh?other=something&another=something&dns=vMEBAAABAAAAAAAAB25vZXJyb3IEdGVzdAAAAQAB')
+ check_ok(req, desc)
+ end
+
+ local function test_get_other_params_after_dns()
+ local desc = 'GET query with other parameters after dns is valid'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path',
+ '/doh?dns=vMEBAAABAAAAAAAAB25vZXJyb3IEdGVzdAAAAQAB&other=something&another=something')
+ check_ok(req, desc)
+ end
+
+ local function test_get_other_params()
+ local desc = 'GET query with other parameters than dns on both sides is valid'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path',
+ '/doh?other=something&dns=vMEBAAABAAAAAAAAB25vZXJyb3IEdGVzdAAAAQAB&another=something')
+ check_ok(req, desc)
+ end
+
+ -- test an invalid DNS query using GET
+ local function test_get_long_input()
+ local req = assert(req_templ:clone())
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path', '/doh?dns=' .. basexx.to_url64(string.rep('\0', 1030)))
+ check_err(req, '414', 'too long GET finishes with 414')
+ end
+
+ local function test_get_no_dns_param()
+ local req = assert(req_templ:clone())
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path', '/doh?notdns=' .. basexx.to_url64(string.rep('\0', 1024)))
+ check_err(req, '400', 'GET without dns parameter finishes with 400')
+ end
+
+ local function test_get_unparseable()
+ local req = assert(req_templ:clone())
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path', '/doh??dns=' .. basexx.to_url64(string.rep('\0', 1024)))
+ check_err(req, '400', 'unparseable GET finishes with 400')
+ end
+
+ local function test_get_invalid_b64()
+ local req = assert(req_templ:clone())
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path', '/doh?dns=thisisnotb64')
+ check_err(req, '400', 'GET with invalid base64 finishes with 400')
+ end
+
+ local function test_get_invalid_chars()
+ local req = assert(req_templ:clone())
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path', '/doh?dns=' .. basexx.to_url64(string.rep('\0', 200)) .. '@#$%?!')
+ check_err(req, '400', 'GET with invalid characters in b64 finishes with 400')
+ end
+
+ local function test_unsupp_method()
+ local req = assert(req_templ:clone())
+ req.headers:upsert(':method', 'PUT')
+ check_err(req, '405', 'unsupported method finishes with 405')
+ end
+
+ local function test_dstaddr()
+ local triggered = false
+ local exp_dstaddr = ffi.gc(ffi.C.kr_straddr_socket(host, port, nil), ffi.C.free)
+ local function check_dstaddr(state, req)
+ triggered = true
+ same(ffi.C.kr_sockaddr_cmp(req.qsource.dst_addr, exp_dstaddr), 0,
+ 'request has correct server address')
+ return state
+ end
+ policy.add(policy.suffix(check_dstaddr, policy.todnames({'dstaddr.test'})))
+ local desc = 'valid POST query has server address available in request'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'POST')
+ req:set_body(basexx.from_base64( -- dstaddr.test. A
+ 'FnkBAAABAAAAAAAAB2RzdGFkZHIEdGVzdAAAAQAB'))
+ check_ok(req, desc)
+ ok(triggered, 'dstaddr policy was triggered')
+ end
+
+ local function test_srcaddr()
+ modules.load('view')
+ assert(view)
+ local policy_refuse = policy.suffix(policy.REFUSE, policy.todnames({'srcaddr.test.knot-resolver.cz'}))
+ -- these netmasks would not work if the request did not contain IP addresses
+ view:addr('0.0.0.0/0', policy_refuse)
+ view:addr('::/0', policy_refuse)
+
+ local desc = 'valid POST query has source address available in request'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'POST')
+ req:set_body(basexx.from_base64( -- srcaddr.test.knot-resolver.cz TXT
+ 'QNQBAAABAAAAAAAAB3NyY2FkZHIEdGVzdA1rbm90LXJlc29sdmVyAmN6AAAQAAE'))
+ local _, pkt = check_ok(req, desc)
+ same(pkt:rcode(), kres.rcode.REFUSED, desc .. ': view module caught it')
+
+ 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())
+-- req.headers:upsert(':method', 'POST')
+-- req.headers:upsert('accept', 'application/dns+json')
+-- req:set_body(string.rep('\0', 12)) -- valid message
+-- check_err(req, '406', 'unsupported Accept type finishes with 406')
+-- end
+
+ -- plan tests
+ local tests = {
+ start_server,
+ test_post_servfail,
+ test_post_noerror,
+ test_post_nxdomain,
+ test_huge_answer,
+ test_post_short_input,
+ test_post_long_input,
+ test_post_unparseable_input,
+ test_post_unsupp_type,
+ test_get_servfail,
+ test_get_noerror,
+ test_get_nxdomain,
+ test_get_other_params_before_dns,
+ test_get_other_params_after_dns,
+ test_get_other_params,
+ test_get_long_input,
+ test_get_no_dns_param,
+ test_get_unparseable,
+ test_get_invalid_b64,
+ test_get_invalid_chars,
+ test_unsupp_method,
+ test_dstaddr,
+ test_srcaddr,
+ test_dns_query_endpoint,
+ }
+
+ return tests
+end
diff --git a/modules/http/http_tls_cert.lua b/modules/http/http_tls_cert.lua
new file mode 100644
index 0000000..7e557c4
--- /dev/null
+++ b/modules/http/http_tls_cert.lua
@@ -0,0 +1,186 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+--[[
+ Conventions:
+ - key = private+public key-pair in openssl.pkey format
+ - certs = lua list of certificates (at least one), each in openssl.x509 format,
+ ordered from leaf to almost-root
+ - panic('...') is used on bad problems instead of returning nils or such
+--]]
+local tls_cert = {}
+
+local ffi = require('ffi')
+local x509, pkey = require('openssl.x509'), require('openssl.pkey')
+
+-- @function Create self-signed certificate; return certs, key
+local function new_ephemeral(host)
+ -- Import luaossl directly
+ local name = require('openssl.x509.name')
+ local altname = require('openssl.x509.altname')
+ local openssl_bignum = require('openssl.bignum')
+ local openssl_rand = require('openssl.rand')
+ -- Create self-signed certificate
+ host = host or hostname()
+ local crt = x509.new()
+ local now = os.time()
+ crt:setVersion(3)
+ -- serial needs to be unique or browsers will show uninformative error messages
+ crt:setSerial(openssl_bignum.fromBinary(openssl_rand.bytes(16)))
+ -- use the host we're listening on as canonical name
+ local dn = name.new()
+ dn:add("CN", host)
+ crt:setSubject(dn)
+ crt:setIssuer(dn) -- should match subject for a self-signed
+ local alt = altname.new()
+ alt:add("DNS", host)
+ crt:setSubjectAlt(alt)
+ -- Valid for 90 days
+ crt:setLifetime(now, now + 90*60*60*24)
+ -- Can't be used as a CA
+ crt:setBasicConstraints{CA=false}
+ crt:setBasicConstraintsCritical(true)
+ -- Create and set key (default: EC/P-256 as a most "interoperable")
+ local key = pkey.new {type = 'EC', curve = 'prime256v1'}
+ crt:setPublicKey(key)
+ crt:sign(key)
+ return { crt }, key
+end
+
+-- @function Write certs and key to files
+local function write_cert_files(certs, key, certfile, keyfile)
+ -- Write certs
+ local f = assert(io.open(certfile, 'w'), string.format('cannot open "%s" for writing', certfile))
+ for _, cert in ipairs(certs) do
+ f:write(tostring(cert))
+ end
+ f:close()
+ -- Write key as a pair
+ f = assert(io.open(keyfile, 'w'), string.format('cannot open "%s" for writing', keyfile))
+ local pub, priv = key:toPEM('public', 'private')
+ assert(f:write(pub .. priv))
+ f:close()
+end
+
+-- @function Start maintenance of a self-signed TLS context (at ephem_state.ctx).
+-- Keep updating the ephem_state.servers table. Stop updating by calling _destroy().
+-- TODO: each process maintains its own ephemeral cert ATM, and the files aren't ever read from.
+function tls_cert.ephemeral_state_maintain(ephem_state, certfile, keyfile)
+ local certs, key = new_ephemeral()
+ write_cert_files(certs, key, certfile, keyfile)
+ ephem_state.ctx = tls_cert.new_tls_context(certs, key)
+ -- Each server needs to have its ctx updated.
+ for _, s in pairs(ephem_state.servers) do
+ s.server.ctx = ephem_state.ctx
+ s.config.ctx = ephem_state.ctx -- not required, but let's keep it synchronized
+ end
+ log_info(ffi.C.LOG_GRP_HTTP, 'created new ephemeral TLS certificate')
+ local _, expiry_stamp = certs[1]:getLifetime()
+ local wait_msec = 1000 * math.max(1, expiry_stamp - os.time() - 3 * 24 * 3600)
+ if not ephem_state.timer_id then
+ ephem_state.timer_id = event.after(wait_msec, function ()
+ tls_cert.ephemeral_state_maintain(ephem_state, certfile, keyfile)
+ end)
+ else
+ event.reschedule(ephem_state.timer_id, wait_msec)
+ end
+end
+function tls_cert.ephemeral_state_destroy(ephem_state)
+ if ephem_state and ephem_state.timer_id then
+ event.cancel(ephem_state.timer_id)
+ end
+end
+
+-- @function Read a certificate chain and a key from files; return certs, key
+function tls_cert.load(certfile, keyfile)
+ -- get key
+ local f, err = io.open(keyfile, 'r')
+ if not f then
+ panic('[http] unable to open TLS key file: %s', err)
+ end
+ local key = pkey.new(f:read('*all'))
+ f:close()
+ if not key then
+ panic('[http] unable to parse TLS key file %s', keyfile)
+ end
+
+ -- get certs list
+ local certs = {}
+ local f, err = io.open(certfile, 'r')
+ if not f then
+ panic('[http] unable to read TLS certificate file: %s', err)
+ end
+ while true do
+ -- Get the next "block" = single certificate as PEM string.
+ local block = nil
+ local line
+ repeat
+ line = f:read()
+ if not line then break end
+ if block then
+ block = block .. '\n' .. line
+ else
+ block = line
+ end
+ -- separator: "posteb" in https://tools.ietf.org/html/rfc7468#section-3
+ until string.sub(line, 1, 9) == '-----END '
+ -- Empty block means clean EOF.
+ if not block then break end
+ if not line then
+ panic('[http] unable to parse TLS certificate file %s, certificate number %d', certfile, 1 + #certs)
+ end
+
+ -- Parse the cert and append to the list.
+ local cert = x509.new(block, 'PEM')
+ if not cert then
+ panic('[http] unable to parse TLS certificate file %s, certificate number %d', certfile, 1 + #certs)
+ end
+ table.insert(certs, cert)
+ end
+ f:close()
+
+ return certs, key
+end
+
+
+-- @function Prefer HTTP/2 or HTTP/1.1
+local function alpnselect(_, protos)
+ for _, proto in ipairs(protos) do
+ if proto == 'h2' or proto == 'http/1.1' then
+ return proto
+ end
+ end
+ return nil
+end
+
+local warned_old_luaossl = false
+
+-- @function Return a new TLS context for a server.
+function tls_cert.new_tls_context(certs, key)
+ local ctx = require('http.tls').new_server_context()
+ if ctx.setAlpnSelect then
+ ctx:setAlpnSelect(alpnselect)
+ end
+ assert(ctx:setPrivateKey(key))
+ assert(ctx:setCertificate(certs[1]))
+
+ -- Set up certificate chain to be sent, if required and possible.
+ if #certs == 1 then return ctx end
+ if ctx.setCertificateChain then
+ local chain = require('openssl.x509.chain').new()
+ assert(chain)
+ for i = 2, #certs do
+ chain:add(certs[i])
+ assert(chain)
+ end
+ assert(ctx:setCertificateChain(chain))
+ elseif not warned_old_luaossl then
+ -- old luaossl version -> only final cert sent to clients
+ log_warn(ffi.C.LOG_GRP_HTTP,
+ 'need luaossl >= 20181207 to support sending intermediary certificate to clients')
+ warned_old_luaossl = true
+ end
+ return ctx
+end
+
+
+return tls_cert
+
diff --git a/modules/http/http_trace.lua b/modules/http/http_trace.lua
new file mode 100644
index 0000000..123cd52
--- /dev/null
+++ b/modules/http/http_trace.lua
@@ -0,0 +1,77 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local ffi = require('ffi')
+local condition = require('cqueues.condition')
+
+-- Trace execution of DNS queries
+local function serve_trace(h, _)
+ local path = h:get(':path')
+ local qname, qtype_str = path:match('/trace/([^/]+)/?([^/]*)')
+ if not qname then
+ return 400, 'expected /trace/<query name>/<query type>'
+ end
+
+ -- Parse query type (or default to A)
+ if not qtype_str or #qtype_str == 0 then
+ qtype_str = 'A'
+ end
+
+ local qtype = kres.type[qtype_str]
+ if not qtype then
+ return 400, string.format('unexpected query type: %s', qtype_str)
+ end
+
+ -- Create logging handler callback
+ local buffer = {}
+ local buffer_log_cb = ffi.cast('trace_log_f', function (_, msg)
+ jit.off(true, true) -- JIT for (C -> lua)^2 nesting isn't allowed
+ table.insert(buffer, ffi.string(msg))
+ end)
+
+ -- Wait for the result of the query
+ -- Note: We can't do non-blocking write to stream directly from resolve callbacks
+ -- because they don't run inside cqueue.
+ local cond = condition.new()
+ local waiting, done = false, false
+ local finish_cb = ffi.cast('trace_callback_f', function (req)
+ jit.off(true, true) -- JIT for (C -> lua)^2 nesting isn't allowed
+ table.insert(buffer, req:selected_tostring())
+ if waiting then
+ cond:signal()
+ end
+ done = true
+ end)
+
+ -- Resolve query and buffer logs into table
+ resolve {
+ name = qname,
+ type = qtype,
+ options = {'TRACE'},
+ init = function (req)
+ req:trace_chain_callbacks(buffer_log_cb, finish_cb)
+ end
+ }
+
+ -- Wait for asynchronous query and free callbacks
+ if not done then
+ waiting = true
+ cond:wait()
+ end
+
+ buffer_log_cb:free()
+ finish_cb:free()
+
+ -- Build the result
+ local result = table.concat(buffer, '')
+ -- Return buffered data
+ if not done then
+ return 504, result
+ end
+ return result
+end
+
+-- Export endpoints
+return {
+ endpoints = {
+ ['/trace'] = {'text/plain', serve_trace},
+ }
+}
diff --git a/modules/http/meson.build b/modules/http/meson.build
new file mode 100644
index 0000000..6705143
--- /dev/null
+++ b/modules/http/meson.build
@@ -0,0 +1,61 @@
+# LUA module: http
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+lua_http_config = configuration_data()
+lua_http_config.set('modules_dir', modules_dir)
+
+lua_http = configure_file(
+ input: 'http.lua.in',
+ output: 'http.lua',
+ configuration: lua_http_config,
+)
+
+lua_mod_src += [
+ lua_http,
+ files('http_doh.lua'),
+ files('http_trace.lua'),
+ files('http_tls_cert.lua'),
+ files('prometheus.lua'),
+]
+
+config_tests += [
+ ['http', files('http.test.lua')],
+ ['http.doh', files('http_doh.test.lua')],
+ ['http.tls', files('test_tls/tls.test.lua')],
+]
+
+# install static files
+install_subdir(
+ 'static',
+ strip_directory: true,
+ exclude_files: [
+ 'bootstrap.min.css.spdx',
+ 'bootstrap.min.js.spdx',
+ 'bootstrap-theme.min.css.spdx',
+ 'datamaps.world.min.spdx',
+ 'dygraph.min.js.spdx',
+ 'd3.spdx',
+ 'epoch.spdx',
+ 'glyphicons-halflings-regular.spdx',
+ 'jquery.spdx',
+ 'selectize.spdx',
+ 'topojson.spdx',
+ ],
+ install_dir: modules_dir / 'http',
+)
+
+# auxiliary debug library for HTTP module
+if openssl.found()
+ debug_opensslkeylog_mod = shared_module(
+ 'debug_opensslkeylog',
+ ['debug_opensslkeylog.c'],
+ # visibility=default == public is required for LD_PRELOAD trick
+ c_args: '-fvisibility=default',
+ name_prefix: '',
+ install: true,
+ install_dir: lib_dir,
+ dependencies: [
+ openssl,
+ ],
+ )
+endif
diff --git a/modules/http/prometheus.lua b/modules/http/prometheus.lua
new file mode 100644
index 0000000..3218552
--- /dev/null
+++ b/modules/http/prometheus.lua
@@ -0,0 +1,178 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- Module implementation
+local M = {
+ namespace = '',
+ finalize = function (_ --[[metrics]]) end,
+}
+
+-- Gauge metrics
+local gauges = {
+ ['worker.concurrent'] = true,
+ ['worker.rss'] = true,
+}
+
+local function merge(t, results, prefix)
+ for _, result in pairs(results) do
+ if type(result) == 'table' then
+ for k, v in pairs(result) do
+ local val = t[prefix..k]
+ t[prefix..k] = (val or 0) + v
+ end
+ end
+ end
+end
+
+local function getstats()
+ local t = {}
+ merge(t, map 'stats.list()', '')
+ merge(t, map 'cache.stats()', 'cache.')
+ merge(t, map 'worker.stats()', 'worker.')
+ return t
+end
+
+-- @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
+ 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
+ if gi then
+ upstreams[k] = {data=v, location=gi.location, country=gi.country and gi.country.iso_code}
+ end
+ 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
+ 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)
+ local ok = true
+ -- Publish stats updates periodically
+ local prev = getstats()
+ while ok do
+ worker.sleep(1)
+ local update
+ prev, update = snapshot_start(prev)
+ local push = tojson(update)
+ ok = ws:send(push)
+ end
+end
+
+-- Transform metrics from Graphite to Prometheus format
+-- See: https://gitlab.nic.cz/knot/knot-resolver/-/issues/650
+-- E.g.:
+-- worker.ipv4 -> worker_ipv4
+-- answer.blocked;stype=A -> answer_blocked{stype="A"}
+local function get_metric(key)
+ local key_index, key_len, key_tag = 0, #key, 0
+ return select(1, key:gsub('.', function (c)
+ key_index = key_index + 1
+ if key_tag == 0 then
+ if c == '.' then return '_' end
+ if c == ';' then key_tag = 1; return '{' end
+ elseif key_tag == 1 then
+ if key_index == key_len then
+ if c == '=' then return '=""}'
+ else return c .. '"}' end
+ end
+ if c == '=' then key_tag = 2; return '="' end
+ elseif key_tag == 2 then
+ if key_index == key_len then
+ if c == ';' then return '"}'
+ else return c .. '"}' end
+ end
+ if c == ';' then key_tag = 1; return '",' end
+ end
+ return nil
+ end))
+end
+
+-- Render stats in Prometheus text format
+local function serve_prometheus()
+ -- First aggregate metrics list and print counters
+ local slist, render = getstats(), {}
+ local latency = {}
+ local counter = '# TYPE %s counter\n%s %f'
+ for k,v in pairs(slist) do
+ k = get_metric(k)
+ -- Aggregate histograms
+ local band = k:match('answer_([%d]+)ms')
+ if band then
+ table.insert(latency, {band, v})
+ elseif k == 'answer_slow' then
+ table.insert(latency, {'+Inf', v})
+ -- Counter as a fallback
+ else
+ local key = M.namespace .. k
+ local name, label = key:match('^([^{]+)(.*)$')
+ table.insert(render, string.format(counter, name, name .. label, v))
+ end
+ end
+ -- Fill in latency histogram
+ local function kweight(x) return tonumber(x) or math.huge end
+ table.sort(latency, function (a,b) return kweight(a[1]) < kweight(b[1]) end)
+ table.insert(render, string.format('# TYPE %slatency histogram', M.namespace))
+ local count, sum = 0.0, 0.0
+ for _,e in ipairs(latency) do
+ -- The information about the %Inf bin is lost, so we treat it
+ -- as a timeout (3000ms) for metrics purposes
+ count = count + e[2]
+ sum = sum + e[2] * (math.min(tonumber(e[1]), 3000.0))
+ table.insert(render, string.format('%slatency_bucket{le="%s"} %f', M.namespace, e[1], count))
+ end
+ table.insert(render, string.format('%slatency_count %f', M.namespace, count))
+ table.insert(render, string.format('%slatency_sum %f', M.namespace, sum))
+ -- Finalize metrics table before rendering
+ if type(M.finalize) == 'function' then
+ M.finalize(render)
+ end
+ return table.concat(render, '\n') .. '\n'
+end
+
+-- Export module interface
+M.endpoints = {
+ ['/stats'] = {'application/json', getstats, stream_stats},
+ ['/frequent'] = {'application/json', function () return stats.frequent() end},
+ ['/upstreams'] = {'application/json', function () return stats.upstreams() end},
+ ['/bogus'] = {'application/json', function () return bogus_log.frequent() end},
+ ['/metrics'] = {'text/plain; version=0.0.4', serve_prometheus},
+}
+
+return M
diff --git a/modules/http/prometheus.rst b/modules/http/prometheus.rst
new file mode 100644
index 0000000..acd8a82
--- /dev/null
+++ b/modules/http/prometheus.rst
@@ -0,0 +1,45 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-http-prometheus:
+
+Prometheus metrics endpoint
+---------------------------
+
+The :ref:`HTTP module <mod-http>` exposes ``/metrics`` endpoint that serves metrics
+from :ref:`mod-stats` in Prometheus_ text format.
+You can use it as soon as HTTP module is configured:
+
+.. code-block:: bash
+
+ $ curl -k https://localhost:8453/metrics | tail
+ # TYPE latency histogram
+ latency_bucket{le=10} 2.000000
+ latency_bucket{le=50} 2.000000
+ latency_bucket{le=100} 2.000000
+ latency_bucket{le=250} 2.000000
+ latency_bucket{le=500} 2.000000
+ latency_bucket{le=1000} 2.000000
+ latency_bucket{le=1500} 2.000000
+ latency_bucket{le=+Inf} 2.000000
+ latency_count 2.000000
+ latency_sum 11.000000
+
+You can namespace the metrics in configuration, using `http.prometheus.namespace` attribute:
+
+.. code-block:: lua
+
+ modules.load('http')
+ -- Set Prometheus namespace
+ http.prometheus.namespace = 'resolver_'
+
+You can also add custom metrics or rewrite existing metrics before they are returned to Prometheus client.
+
+.. code-block:: lua
+
+ modules.load('http')
+ -- Add an arbitrary metric to Prometheus
+ http.prometheus.finalize = function (metrics)
+ table.insert(metrics, 'build_info{version="1.2.3"} 1')
+ end
+
+.. _Prometheus: https://prometheus.io
diff --git a/modules/http/static/bootstrap-theme.min.css b/modules/http/static/bootstrap-theme.min.css
new file mode 100644
index 0000000..449915d
--- /dev/null
+++ b/modules/http/static/bootstrap-theme.min.css
@@ -0,0 +1,6 @@
+/*!
+ * Bootstrap v3.3.6 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * SPDX-License-Identifier: MIT
+ */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)}
+/*# sourceMappingURL=bootstrap-theme.min.css.map */
diff --git a/modules/http/static/bootstrap-theme.min.css.spdx b/modules/http/static/bootstrap-theme.min.css.spdx
new file mode 100644
index 0000000..a56f287
--- /dev/null
+++ b/modules/http/static/bootstrap-theme.min.css.spdx
@@ -0,0 +1,11 @@
+SPDXVersion: SPDX-2.1
+DataLicense: CC0-1.0
+SPDXID: SPDXRef-DOCUMENT
+DocumentName: bootstrap-theme
+DocumentNamespace: http://spdx.org/spdxdocs/spdx-v2.1-2794db89-37c2-415b-b1bd-d66b445c5202
+
+PackageName: bootstrap-theme
+PackageVersion: 3.3.6
+PackageDownloadLocation: git+https://github.com/twbs/bootstrap.git@81df608a40bf0629a1dc08e584849bb1e43e0b7a#dist/css/bootstrap-theme.min.css
+PackageOriginator: Organization: Twitter
+PackageLicenseDeclared: MIT
diff --git a/modules/http/static/bootstrap.min.css b/modules/http/static/bootstrap.min.css
new file mode 100644
index 0000000..3bda7c6
--- /dev/null
+++ b/modules/http/static/bootstrap.min.css
@@ -0,0 +1,11 @@
+/*!
+ * bootswatch v3.3.6+2 yeti
+ * Homepage: http://bootswatch.com
+ * Copyright 2012-2016 Thomas Park
+ * SPDX-License-Identifier: MIT
+ * Based on Bootstrap
+*//*!
+ * Bootstrap v3.3.6 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{background:transparent !important;color:#000 !important;-webkit-box-shadow:none !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:'Glyphicons Halflings';src:url('../glyphicons-halflings-regular.eot');src:url('../glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../glyphicons-halflings-regular.woff2') format('woff2'),url('../glyphicons-halflings-regular.woff') format('woff'),url('../glyphicons-halflings-regular.ttf') format('truetype'),url('../glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-euro:before,.glyphicon-eur:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#222222;background-color:#ffffff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#008cba;text-decoration:none}a:hover,a:focus{color:#008cba;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:0}.img-thumbnail{padding:4px;line-height:1.4;background-color:#ffffff;border:1px solid #dddddd;border-radius:0;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:21px;margin-bottom:21px;border:0;border-top:1px solid #dddddd}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role="button"]{cursor:pointer}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:normal;line-height:1;color:#999999}h1,.h1,h2,.h2,h3,.h3{margin-top:21px;margin-bottom:10.5px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10.5px;margin-bottom:10.5px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:39px}h2,.h2{font-size:32px}h3,.h3{font-size:26px}h4,.h4{font-size:19px}h5,.h5{font-size:15px}h6,.h6{font-size:13px}p{margin:0 0 10.5px}.lead{margin-bottom:21px;font-size:17px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:22.5px}}small,.small{font-size:80%}mark,.mark{background-color:#fcf8e3;padding:.2em}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#999999}.text-primary{color:#008cba}a.text-primary:hover,a.text-primary:focus{color:#006687}.text-success{color:#43ac6a}a.text-success:hover,a.text-success:focus{color:#358753}.text-info{color:#5bc0de}a.text-info:hover,a.text-info:focus{color:#31b0d5}.text-warning{color:#e99002}a.text-warning:hover,a.text-warning:focus{color:#b67102}.text-danger{color:#f04124}a.text-danger:hover,a.text-danger:focus{color:#d32a0e}.bg-primary{color:#fff;background-color:#008cba}a.bg-primary:hover,a.bg-primary:focus{background-color:#006687}.bg-success{background-color:#dff0d8}a.bg-success:hover,a.bg-success:focus{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover,a.bg-info:focus{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover,a.bg-warning:focus{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover,a.bg-danger:focus{background-color:#e4b9b9}.page-header{padding-bottom:9.5px;margin:42px 0 21px;border-bottom:1px solid #dddddd}ul,ol{margin-top:0;margin-bottom:10.5px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:21px}dt,dd{line-height:1.4}dt{font-weight:bold}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999999}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10.5px 21px;margin:0 0 21px;font-size:18.75px;border-left:5px solid #dddddd}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.4;color:#6f6f6f}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #dddddd;border-left:0;text-align:right}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}address{margin-bottom:21px;font-style:normal;line-height:1.4}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:0}kbd{padding:2px 4px;font-size:90%;color:#ffffff;background-color:#333333;border-radius:0;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:bold;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:10px;margin:0 0 10.5px;font-size:14px;line-height:1.4;word-break:break-all;word-wrap:break-word;color:#333333;background-color:#f5f5f5;border:1px solid #cccccc;border-radius:0}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0%}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0%}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0%}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0%}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#999999;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:21px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.4;vertical-align:top;border-top:1px solid #dddddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #dddddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #dddddd}.table .table{background-color:#ffffff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #dddddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #dddddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}.table-responsive{overflow-x:auto;min-height:0.01%}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15.75px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #dddddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:21px;font-size:22.5px;line-height:inherit;color:#333333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:9px;font-size:15px;line-height:1.4;color:#6f6f6f}.form-control{display:block;width:100%;height:39px;padding:8px 12px;font-size:15px;line-height:1.4;color:#6f6f6f;background-color:#ffffff;background-image:none;border:1px solid #cccccc;border-radius:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control::-moz-placeholder{color:#999999;opacity:1}.form-control:-ms-input-placeholder{color:#999999}.form-control::-webkit-input-placeholder{color:#999999}.form-control::-ms-expand{border:0;background-color:transparent}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eeeeee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type="search"]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type="date"].form-control,input[type="time"].form-control,input[type="datetime-local"].form-control,input[type="month"].form-control{line-height:39px}input[type="date"].input-sm,input[type="time"].input-sm,input[type="datetime-local"].input-sm,input[type="month"].input-sm,.input-group-sm input[type="date"],.input-group-sm input[type="time"],.input-group-sm input[type="datetime-local"],.input-group-sm input[type="month"]{line-height:36px}input[type="date"].input-lg,input[type="time"].input-lg,input[type="datetime-local"].input-lg,input[type="month"].input-lg,.input-group-lg input[type="date"],.input-group-lg input[type="time"],.input-group-lg input[type="datetime-local"],.input-group-lg input[type="month"]{line-height:60px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{min-height:21px;padding-left:20px;margin-bottom:0;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{position:absolute;margin-left:-20px;margin-top:4px \9}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"].disabled,input[type="checkbox"].disabled,fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:9px;padding-bottom:9px;margin-bottom:0;min-height:36px}.form-control-static.input-lg,.form-control-static.input-sm{padding-left:0;padding-right:0}.input-sm{height:36px;padding:8px 12px;font-size:12px;line-height:1.5;border-radius:0}select.input-sm{height:36px;line-height:36px}textarea.input-sm,select[multiple].input-sm{height:auto}.form-group-sm .form-control{height:36px;padding:8px 12px;font-size:12px;line-height:1.5;border-radius:0}.form-group-sm select.form-control{height:36px;line-height:36px}.form-group-sm textarea.form-control,.form-group-sm select[multiple].form-control{height:auto}.form-group-sm .form-control-static{height:36px;min-height:33px;padding:9px 12px;font-size:12px;line-height:1.5}.input-lg{height:60px;padding:16px 20px;font-size:19px;line-height:1.3333333;border-radius:0}select.input-lg{height:60px;line-height:60px}textarea.input-lg,select[multiple].input-lg{height:auto}.form-group-lg .form-control{height:60px;padding:16px 20px;font-size:19px;line-height:1.3333333;border-radius:0}.form-group-lg select.form-control{height:60px;line-height:60px}.form-group-lg textarea.form-control,.form-group-lg select[multiple].form-control{height:auto}.form-group-lg .form-control-static{height:60px;min-height:40px;padding:17px 20px;font-size:19px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:48.75px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:39px;height:39px;line-height:39px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg+.form-control-feedback,.form-group-lg .form-control+.form-control-feedback{width:60px;height:60px;line-height:60px}.input-sm+.form-control-feedback,.input-group-sm+.form-control-feedback,.form-group-sm .form-control+.form-control-feedback{width:36px;height:36px;line-height:36px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#43ac6a}.has-success .form-control{border-color:#43ac6a;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#358753;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #85d0a1;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #85d0a1}.has-success .input-group-addon{color:#43ac6a;border-color:#43ac6a;background-color:#dff0d8}.has-success .form-control-feedback{color:#43ac6a}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#e99002}.has-warning .form-control{border-color:#e99002;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#b67102;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #febc53;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #febc53}.has-warning .input-group-addon{color:#e99002;border-color:#e99002;background-color:#fcf8e3}.has-warning .form-control-feedback{color:#e99002}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#f04124}.has-error .form-control{border-color:#f04124;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#d32a0e;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #f79483;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #f79483}.has-error .input-group-addon{color:#f04124;border-color:#f04124;background-color:#f2dede}.has-error .form-control-feedback{color:#f04124}.has-feedback label~.form-control-feedback{top:26px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#626262}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:9px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:30px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}@media (min-width:768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:9px}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:17px;font-size:19px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:9px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:8px 12px;font-size:15px;line-height:1.4;border-radius:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn.active.focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#333333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333333;background-color:#e7e7e7;border-color:#cccccc}.btn-default:focus,.btn-default.focus{color:#333333;background-color:#cecece;border-color:#8c8c8c}.btn-default:hover{color:#333333;background-color:#cecece;border-color:#adadad}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#333333;background-color:#cecece;border-color:#adadad}.btn-default:active:hover,.btn-default.active:hover,.open>.dropdown-toggle.btn-default:hover,.btn-default:active:focus,.btn-default.active:focus,.open>.dropdown-toggle.btn-default:focus,.btn-default:active.focus,.btn-default.active.focus,.open>.dropdown-toggle.btn-default.focus{color:#333333;background-color:#bcbcbc;border-color:#8c8c8c}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled.focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default.focus{background-color:#e7e7e7;border-color:#cccccc}.btn-default .badge{color:#e7e7e7;background-color:#333333}.btn-primary{color:#ffffff;background-color:#008cba;border-color:#0079a1}.btn-primary:focus,.btn-primary.focus{color:#ffffff;background-color:#006687;border-color:#001921}.btn-primary:hover{color:#ffffff;background-color:#006687;border-color:#004b63}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#ffffff;background-color:#006687;border-color:#004b63}.btn-primary:active:hover,.btn-primary.active:hover,.open>.dropdown-toggle.btn-primary:hover,.btn-primary:active:focus,.btn-primary.active:focus,.open>.dropdown-toggle.btn-primary:focus,.btn-primary:active.focus,.btn-primary.active.focus,.open>.dropdown-toggle.btn-primary.focus{color:#ffffff;background-color:#004b63;border-color:#001921}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled.focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary.focus{background-color:#008cba;border-color:#0079a1}.btn-primary .badge{color:#008cba;background-color:#ffffff}.btn-success{color:#ffffff;background-color:#43ac6a;border-color:#3c9a5f}.btn-success:focus,.btn-success.focus{color:#ffffff;background-color:#358753;border-color:#183e26}.btn-success:hover{color:#ffffff;background-color:#358753;border-color:#2b6e44}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#ffffff;background-color:#358753;border-color:#2b6e44}.btn-success:active:hover,.btn-success.active:hover,.open>.dropdown-toggle.btn-success:hover,.btn-success:active:focus,.btn-success.active:focus,.open>.dropdown-toggle.btn-success:focus,.btn-success:active.focus,.btn-success.active.focus,.open>.dropdown-toggle.btn-success.focus{color:#ffffff;background-color:#2b6e44;border-color:#183e26}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled.focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success.focus{background-color:#43ac6a;border-color:#3c9a5f}.btn-success .badge{color:#43ac6a;background-color:#ffffff}.btn-info{color:#ffffff;background-color:#5bc0de;border-color:#46b8da}.btn-info:focus,.btn-info.focus{color:#ffffff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#ffffff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#ffffff;background-color:#31b0d5;border-color:#269abc}.btn-info:active:hover,.btn-info.active:hover,.open>.dropdown-toggle.btn-info:hover,.btn-info:active:focus,.btn-info.active:focus,.open>.dropdown-toggle.btn-info:focus,.btn-info:active.focus,.btn-info.active.focus,.open>.dropdown-toggle.btn-info.focus{color:#ffffff;background-color:#269abc;border-color:#1b6d85}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled.focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info.focus{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#ffffff}.btn-warning{color:#ffffff;background-color:#e99002;border-color:#d08002}.btn-warning:focus,.btn-warning.focus{color:#ffffff;background-color:#b67102;border-color:#513201}.btn-warning:hover{color:#ffffff;background-color:#b67102;border-color:#935b01}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#ffffff;background-color:#b67102;border-color:#935b01}.btn-warning:active:hover,.btn-warning.active:hover,.open>.dropdown-toggle.btn-warning:hover,.btn-warning:active:focus,.btn-warning.active:focus,.open>.dropdown-toggle.btn-warning:focus,.btn-warning:active.focus,.btn-warning.active.focus,.open>.dropdown-toggle.btn-warning.focus{color:#ffffff;background-color:#935b01;border-color:#513201}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled.focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning.focus{background-color:#e99002;border-color:#d08002}.btn-warning .badge{color:#e99002;background-color:#ffffff}.btn-danger{color:#ffffff;background-color:#f04124;border-color:#ea2f10}.btn-danger:focus,.btn-danger.focus{color:#ffffff;background-color:#d32a0e;border-color:#731708}.btn-danger:hover{color:#ffffff;background-color:#d32a0e;border-color:#b1240c}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#ffffff;background-color:#d32a0e;border-color:#b1240c}.btn-danger:active:hover,.btn-danger.active:hover,.open>.dropdown-toggle.btn-danger:hover,.btn-danger:active:focus,.btn-danger.active:focus,.open>.dropdown-toggle.btn-danger:focus,.btn-danger:active.focus,.btn-danger.active.focus,.open>.dropdown-toggle.btn-danger.focus{color:#ffffff;background-color:#b1240c;border-color:#731708}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled.focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger.focus{background-color:#f04124;border-color:#ea2f10}.btn-danger .badge{color:#f04124;background-color:#ffffff}.btn-link{color:#008cba;font-weight:normal;border-radius:0}.btn-link,.btn-link:active,.btn-link.active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#008cba;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999999;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:16px 20px;font-size:19px;line-height:1.3333333;border-radius:0}.btn-sm,.btn-group-sm>.btn{padding:8px 12px;font-size:12px;border-radius:0}.btn-xs,.btn-group-xs>.btn{padding:4px 6px;font-size:12px;line-height:1.5;border-radius:0}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-property:height, visibility;-o-transition-property:height, visibility;transition-property:height, visibility;-webkit-transition-duration:0.35s;-o-transition-duration:0.35s;transition-duration:0.35s;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid \9;border-right:4px solid transparent;border-left:4px solid transparent}.dropup,.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:15px;text-align:left;background-color:#ffffff;border:1px solid #cccccc;border:1px solid rgba(0,0,0,0.15);border-radius:0;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);-webkit-background-clip:padding-box;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9.5px 0;overflow:hidden;background-color:rgba(0,0,0,0.2)}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.4;color:#555555;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#262626;background-color:#eeeeee}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#ffffff;text-decoration:none;outline:0;background-color:#008cba}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.4;color:#999999;white-space:nowrap}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px dashed;border-bottom:4px solid \9;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:0;border-top-left-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-right-radius:0;border-top-left-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle="buttons"]>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn input[type="checkbox"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:60px;padding:16px 20px;font-size:19px;line-height:1.3333333;border-radius:0}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:60px;line-height:60px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:36px;padding:8px 12px;font-size:12px;line-height:1.5;border-radius:0}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:36px;line-height:36px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:8px 12px;font-size:15px;font-weight:normal;line-height:1;color:#6f6f6f;text-align:center;background-color:#eeeeee;border:1px solid #cccccc;border-radius:0}.input-group-addon.input-sm{padding:8px 12px;font-size:12px;border-radius:0}.input-group-addon.input-lg{padding:16px 20px;font-size:19px;border-radius:0}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eeeeee}.nav>li.disabled>a{color:#999999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999999;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eeeeee;border-color:#008cba}.nav .nav-divider{height:1px;margin:9.5px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #dddddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.4;border:1px solid transparent;border-radius:0 0 0 0}.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #dddddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#6f6f6f;background-color:#ffffff;border:1px solid #dddddd;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #dddddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #dddddd;border-radius:0 0 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#ffffff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:0}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#ffffff;background-color:#008cba}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #dddddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #dddddd;border-radius:0 0 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#ffffff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:45px;margin-bottom:21px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:0}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:12px 15px;font-size:19px;line-height:21px;height:45px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:5.5px;margin-bottom:5.5px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:0}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:6px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:21px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:21px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:12px;padding-bottom:12px}}.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:3px;margin-bottom:3px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-right-radius:0;border-top-left-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:3px;margin-bottom:3px}.navbar-btn.btn-sm{margin-top:4.5px;margin-bottom:4.5px}.navbar-btn.btn-xs{margin-top:11.5px;margin-bottom:11.5px}.navbar-text{margin-top:12px;margin-bottom:12px}@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}}@media (min-width:768px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#333333;border-color:#222222}.navbar-default .navbar-brand{color:#ffffff}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#ffffff;background-color:transparent}.navbar-default .navbar-text{color:#ffffff}.navbar-default .navbar-nav>li>a{color:#ffffff}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#ffffff;background-color:#272727}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#ffffff;background-color:#272727}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#cccccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:transparent}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:transparent}.navbar-default .navbar-toggle .icon-bar{background-color:#ffffff}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#222222}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#272727;color:#ffffff}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#ffffff}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#ffffff;background-color:#272727}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#ffffff;background-color:#272727}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#cccccc;background-color:transparent}}.navbar-default .navbar-link{color:#ffffff}.navbar-default .navbar-link:hover{color:#ffffff}.navbar-default .btn-link{color:#ffffff}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#ffffff}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#cccccc}.navbar-inverse{background-color:#008cba;border-color:#006687}.navbar-inverse .navbar-brand{color:#ffffff}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#ffffff;background-color:transparent}.navbar-inverse .navbar-text{color:#ffffff}.navbar-inverse .navbar-nav>li>a{color:#ffffff}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#ffffff;background-color:#006687}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#ffffff;background-color:#006687}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:transparent}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:transparent}.navbar-inverse .navbar-toggle .icon-bar{background-color:#ffffff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#007196}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#006687;color:#ffffff}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#006687}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#006687}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#ffffff}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#ffffff;background-color:#006687}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#ffffff;background-color:#006687}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444444;background-color:transparent}}.navbar-inverse .navbar-link{color:#ffffff}.navbar-inverse .navbar-link:hover{color:#ffffff}.navbar-inverse .btn-link{color:#ffffff}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#ffffff}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444444}.breadcrumb{padding:8px 15px;margin-bottom:21px;list-style:none;background-color:#f5f5f5;border-radius:0}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#999999}.breadcrumb>.active{color:#333333}.pagination{display:inline-block;padding-left:0;margin:21px 0;border-radius:0}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:8px 12px;line-height:1.4;text-decoration:none;color:#008cba;background-color:transparent;border:1px solid transparent;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:0;border-top-left-radius:0}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:0;border-top-right-radius:0}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{z-index:2;color:#008cba;background-color:#eeeeee;border-color:transparent}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:3;color:#ffffff;background-color:#008cba;border-color:transparent;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999999;background-color:#ffffff;border-color:transparent;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:16px 20px;font-size:19px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:0;border-top-left-radius:0}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:0;border-top-right-radius:0}.pagination-sm>li>a,.pagination-sm>li>span{padding:8px 12px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:0;border-top-left-radius:0}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:0;border-top-right-radius:0}.pager{padding-left:0;margin:21px 0;list-style:none;text-align:center}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:transparent;border:1px solid transparent;border-radius:3px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eeeeee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999999;background-color:transparent;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-weight:bold;line-height:1;color:#ffffff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#ffffff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#999999}.label-default[href]:hover,.label-default[href]:focus{background-color:#808080}.label-primary{background-color:#008cba}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#006687}.label-success{background-color:#43ac6a}.label-success[href]:hover,.label-success[href]:focus{background-color:#358753}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#e99002}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#b67102}.label-danger{background-color:#f04124}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#d32a0e}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:bold;color:#ffffff;line-height:1;vertical-align:middle;white-space:nowrap;text-align:center;background-color:#008cba;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#ffffff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#008cba;background-color:#ffffff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#fafafa}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:23px;font-weight:200}.jumbotron>hr{border-top-color:#e1e1e1}.container .jumbotron,.container-fluid .jumbotron{border-radius:0;padding-left:15px;padding-right:15px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:68px}}.thumbnail{display:block;padding:4px;margin-bottom:21px;line-height:1.4;background-color:#ffffff;border:1px solid #dddddd;border-radius:0;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-left:auto;margin-right:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#008cba}.thumbnail .caption{padding:9px;color:#222222}.alert{padding:15px;margin-bottom:21px;border:1px solid transparent;border-radius:0}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#43ac6a;border-color:#3c9a5f;color:#ffffff}.alert-success hr{border-top-color:#358753}.alert-success .alert-link{color:#e6e6e6}.alert-info{background-color:#5bc0de;border-color:#3db5d8;color:#ffffff}.alert-info hr{border-top-color:#2aabd2}.alert-info .alert-link{color:#e6e6e6}.alert-warning{background-color:#e99002;border-color:#d08002;color:#ffffff}.alert-warning hr{border-top-color:#b67102}.alert-warning .alert-link{color:#e6e6e6}.alert-danger{background-color:#f04124;border-color:#ea2f10;color:#ffffff}.alert-danger hr{border-top-color:#d32a0e}.alert-danger .alert-link{color:#e6e6e6}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:21px;margin-bottom:21px;background-color:#f5f5f5;border-radius:0;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:21px;color:#ffffff;text-align:center;background-color:#008cba;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#43ac6a}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-warning{background-color:#e99002}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-danger{background-color:#f04124}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{zoom:1;overflow:hidden}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#ffffff;border:1px solid #dddddd}.list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}a.list-group-item,button.list-group-item{color:#555555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333333}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{text-decoration:none;color:#555555;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{background-color:#eeeeee;color:#999999;cursor:not-allowed}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#999999}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#ffffff;background-color:#008cba;border-color:#008cba}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#87e1ff}.list-group-item-success{color:#43ac6a;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#43ac6a}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,button.list-group-item-success:hover,a.list-group-item-success:focus,button.list-group-item-success:focus{color:#43ac6a;background-color:#d0e9c6}a.list-group-item-success.active,button.list-group-item-success.active,a.list-group-item-success.active:hover,button.list-group-item-success.active:hover,a.list-group-item-success.active:focus,button.list-group-item-success.active:focus{color:#fff;background-color:#43ac6a;border-color:#43ac6a}.list-group-item-info{color:#5bc0de;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#5bc0de}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,button.list-group-item-info:hover,a.list-group-item-info:focus,button.list-group-item-info:focus{color:#5bc0de;background-color:#c4e3f3}a.list-group-item-info.active,button.list-group-item-info.active,a.list-group-item-info.active:hover,button.list-group-item-info.active:hover,a.list-group-item-info.active:focus,button.list-group-item-info.active:focus{color:#fff;background-color:#5bc0de;border-color:#5bc0de}.list-group-item-warning{color:#e99002;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#e99002}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,button.list-group-item-warning:hover,a.list-group-item-warning:focus,button.list-group-item-warning:focus{color:#e99002;background-color:#faf2cc}a.list-group-item-warning.active,button.list-group-item-warning.active,a.list-group-item-warning.active:hover,button.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus,button.list-group-item-warning.active:focus{color:#fff;background-color:#e99002;border-color:#e99002}.list-group-item-danger{color:#f04124;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#f04124}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,button.list-group-item-danger:hover,a.list-group-item-danger:focus,button.list-group-item-danger:focus{color:#f04124;background-color:#ebcccc}a.list-group-item-danger.active,button.list-group-item-danger.active,a.list-group-item-danger.active:hover,button.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus,button.list-group-item-danger.active:focus{color:#fff;background-color:#f04124;border-color:#f04124}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:21px;background-color:#ffffff;border:1px solid transparent;border-radius:0;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:-1;border-top-left-radius:-1}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:17px;color:inherit}.panel-title>a,.panel-title>small,.panel-title>.small,.panel-title>small>a,.panel-title>.small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #dddddd;border-bottom-right-radius:-1;border-bottom-left-radius:-1}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:-1;border-top-left-radius:-1}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:-1;border-bottom-left-radius:-1}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-left:15px;padding-right:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:-1;border-top-left-radius:-1}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:-1;border-top-right-radius:-1}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:-1}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:-1}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:-1;border-bottom-left-radius:-1}.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-left-radius:-1;border-bottom-right-radius:-1}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:-1}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:-1}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #dddddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:21px}.panel-group .panel{margin-bottom:0;border-radius:0}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body,.panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid #dddddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #dddddd}.panel-default{border-color:#dddddd}.panel-default>.panel-heading{color:#333333;background-color:#f5f5f5;border-color:#dddddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#dddddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#dddddd}.panel-primary{border-color:#008cba}.panel-primary>.panel-heading{color:#ffffff;background-color:#008cba;border-color:#008cba}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#008cba}.panel-primary>.panel-heading .badge{color:#008cba;background-color:#ffffff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#008cba}.panel-success{border-color:#3c9a5f}.panel-success>.panel-heading{color:#ffffff;background-color:#43ac6a;border-color:#3c9a5f}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#3c9a5f}.panel-success>.panel-heading .badge{color:#43ac6a;background-color:#ffffff}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#3c9a5f}.panel-info{border-color:#3db5d8}.panel-info>.panel-heading{color:#ffffff;background-color:#5bc0de;border-color:#3db5d8}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#3db5d8}.panel-info>.panel-heading .badge{color:#5bc0de;background-color:#ffffff}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#3db5d8}.panel-warning{border-color:#d08002}.panel-warning>.panel-heading{color:#ffffff;background-color:#e99002;border-color:#d08002}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d08002}.panel-warning>.panel-heading .badge{color:#e99002;background-color:#ffffff}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d08002}.panel-danger{border-color:#ea2f10}.panel-danger>.panel-heading{color:#ffffff;background-color:#f04124;border-color:#ea2f10}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ea2f10}.panel-danger>.panel-heading .badge{color:#f04124;background-color:#ffffff}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ea2f10}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#fafafa;border:1px solid #e8e8e8;border-radius:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:0}.well-sm{padding:9px;border-radius:0}.close{float:right;font-size:22.5px;font-weight:bold;line-height:1;color:#ffffff;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#ffffff;text-decoration:none;cursor:pointer;opacity:0.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:hidden;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);-ms-transform:translate(0, -25%);-o-transform:translate(0, -25%);transform:translate(0, -25%);-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#ffffff;border:1px solid #999999;border:1px solid rgba(0,0,0,0.2);border-radius:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);-webkit-background-clip:padding-box;background-clip:padding-box;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:0.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.4}.modal-body{position:relative;padding:20px}.modal-footer{padding:20px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:normal;letter-spacing:normal;line-break:auto;line-height:1.4;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;font-size:12px;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:0.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;background-color:#333333;border-radius:0}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#333333}.tooltip.top-left .tooltip-arrow{bottom:0;right:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#333333}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#333333}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#333333}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#333333}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#333333}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#333333}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#333333}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:normal;letter-spacing:normal;line-break:auto;line-height:1.4;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;font-size:15px;background-color:#333333;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #333333;border:1px solid transparent;border-radius:0;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:15px;background-color:#333333;border-bottom:1px solid #262626;border-radius:-1 -1 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{border-width:10px;content:""}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#000000;border-top-color:rgba(0,0,0,0.05);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#333333}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#000000;border-right-color:rgba(0,0,0,0.05)}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#333333}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#000000;border-bottom-color:rgba(0,0,0,0.05);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#333333}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#000000;border-left-color:rgba(0,0,0,0.05)}.popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#333333;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.next,.carousel-inner>.item.active.right{-webkit-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);left:0}.carousel-inner>.item.prev,.carousel-inner>.item.active.left{-webkit-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0);left:0}.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right,.carousel-inner>.item.active{-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);left:0}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:0.5;filter:alpha(opacity=50);font-size:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6);background-color:rgba(0,0,0,0)}.carousel-control.left{background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-image:-webkit-gradient(linear, left top, right top, from(rgba(0,0,0,0.5)), to(rgba(0,0,0,0.0001)));background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-image:-webkit-gradient(linear, left top, right top, from(rgba(0,0,0,0.0001)), to(rgba(0,0,0,0.5)));background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:hover,.carousel-control:focus{outline:0;color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;margin-top:-10px;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;line-height:1;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #ffffff;border-radius:10px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0)}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#ffffff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-header:before,.modal-header:after,.modal-footer:before,.modal-footer:after{content:" ";display:table}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-header:after,.modal-footer:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none !important}@media (max-width:767px){.visible-xs{display:block !important}table.visible-xs{display:table !important}tr.visible-xs{display:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (max-width:767px){.visible-xs-block{display:block !important}}@media (max-width:767px){.visible-xs-inline{display:inline !important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block !important}table.visible-sm{display:table !important}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block !important}table.visible-md{display:table !important}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block !important}}@media (min-width:1200px){.visible-lg{display:block !important}table.visible-lg{display:table !important}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (min-width:1200px){.visible-lg-block{display:block !important}}@media (min-width:1200px){.visible-lg-inline{display:inline !important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block !important}}@media (max-width:767px){.hidden-xs{display:none !important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none !important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none !important}}@media (min-width:1200px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table !important}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}}.visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}}.visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}}@media print{.hidden-print{display:none !important}}.navbar{border:none;font-size:13px;font-weight:300}.navbar .navbar-toggle:hover .icon-bar{background-color:#b3b3b3}.navbar-collapse{border-top-color:rgba(0,0,0,0.2);-webkit-box-shadow:none;box-shadow:none}.navbar .btn{padding-top:6px;padding-bottom:6px}.navbar-form{margin-top:7px;margin-bottom:5px}.navbar-form .form-control{height:auto;padding:4px 6px}.navbar-text{margin:12px 15px;line-height:21px}.navbar .dropdown-menu{border:none}.navbar .dropdown-menu>li>a,.navbar .dropdown-menu>li>a:focus{background-color:transparent;font-size:13px;font-weight:300}.navbar .dropdown-header{color:rgba(255,255,255,0.5)}.navbar-default .dropdown-menu{background-color:#333333}.navbar-default .dropdown-menu>li>a,.navbar-default .dropdown-menu>li>a:focus{color:#ffffff}.navbar-default .dropdown-menu>li>a:hover,.navbar-default .dropdown-menu>.active>a,.navbar-default .dropdown-menu>.active>a:hover{background-color:#272727}.navbar-inverse .dropdown-menu{background-color:#008cba}.navbar-inverse .dropdown-menu>li>a,.navbar-inverse .dropdown-menu>li>a:focus{color:#ffffff}.navbar-inverse .dropdown-menu>li>a:hover,.navbar-inverse .dropdown-menu>.active>a,.navbar-inverse .dropdown-menu>.active>a:hover{background-color:#006687}.btn{padding:8px 12px}.btn-lg{padding:16px 20px}.btn-sm{padding:8px 12px}.btn-xs{padding:4px 6px}.btn-group .btn~.dropdown-toggle{padding-left:16px;padding-right:16px}.btn-group .dropdown-menu{border-top-width:0}.btn-group.dropup .dropdown-menu{border-top-width:1px;border-bottom-width:0;margin-bottom:0}.btn-group .dropdown-toggle.btn-default~.dropdown-menu{background-color:#e7e7e7;border-color:#cccccc}.btn-group .dropdown-toggle.btn-default~.dropdown-menu>li>a{color:#333333}.btn-group .dropdown-toggle.btn-default~.dropdown-menu>li>a:hover{background-color:#d3d3d3}.btn-group .dropdown-toggle.btn-primary~.dropdown-menu{background-color:#008cba;border-color:#0079a1}.btn-group .dropdown-toggle.btn-primary~.dropdown-menu>li>a{color:#ffffff}.btn-group .dropdown-toggle.btn-primary~.dropdown-menu>li>a:hover{background-color:#006d91}.btn-group .dropdown-toggle.btn-success~.dropdown-menu{background-color:#43ac6a;border-color:#3c9a5f}.btn-group .dropdown-toggle.btn-success~.dropdown-menu>li>a{color:#ffffff}.btn-group .dropdown-toggle.btn-success~.dropdown-menu>li>a:hover{background-color:#388f58}.btn-group .dropdown-toggle.btn-info~.dropdown-menu{background-color:#5bc0de;border-color:#46b8da}.btn-group .dropdown-toggle.btn-info~.dropdown-menu>li>a{color:#ffffff}.btn-group .dropdown-toggle.btn-info~.dropdown-menu>li>a:hover{background-color:#39b3d7}.btn-group .dropdown-toggle.btn-warning~.dropdown-menu{background-color:#e99002;border-color:#d08002}.btn-group .dropdown-toggle.btn-warning~.dropdown-menu>li>a{color:#ffffff}.btn-group .dropdown-toggle.btn-warning~.dropdown-menu>li>a:hover{background-color:#c17702}.btn-group .dropdown-toggle.btn-danger~.dropdown-menu{background-color:#f04124;border-color:#ea2f10}.btn-group .dropdown-toggle.btn-danger~.dropdown-menu>li>a{color:#ffffff}.btn-group .dropdown-toggle.btn-danger~.dropdown-menu>li>a:hover{background-color:#dc2c0f}.lead{color:#6f6f6f}cite{font-style:italic}blockquote{border-left-width:1px;color:#6f6f6f}blockquote.pull-right{border-right-width:1px}blockquote small{font-size:12px;font-weight:300}table{font-size:12px}label,.control-label,.help-block,.checkbox,.radio{font-size:12px;font-weight:normal}input[type="radio"],input[type="checkbox"]{margin-top:1px}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{border-color:transparent}.nav-tabs>li>a{background-color:#e7e7e7;color:#222222}.nav-tabs .caret{border-top-color:#222222;border-bottom-color:#222222}.nav-pills{font-weight:300}.breadcrumb{border:1px solid #dddddd;border-radius:3px;font-size:10px;font-weight:300;text-transform:uppercase}.pagination{font-size:12px;font-weight:300;color:#999999}.pagination>li>a,.pagination>li>span{margin-left:4px;color:#999999}.pagination>.active>a,.pagination>.active>span{color:#fff}.pagination>li>a,.pagination>li:first-child>a,.pagination>li:last-child>a,.pagination>li>span,.pagination>li:first-child>span,.pagination>li:last-child>span{border-radius:3px}.pagination-lg>li>a,.pagination-lg>li>span{padding-left:22px;padding-right:22px}.pagination-sm>li>a,.pagination-sm>li>span{padding:0 5px}.pager{font-size:12px;font-weight:300;color:#999999}.list-group{font-size:12px;font-weight:300}.close{opacity:0.4;text-decoration:none;text-shadow:none}.close:hover,.close:focus{opacity:1}.alert{font-size:12px;font-weight:300}.alert .alert-link{font-weight:normal;color:#fff;text-decoration:underline}.label{padding-left:1em;padding-right:1em;border-radius:0;font-weight:300}.label-default{background-color:#e7e7e7;color:#333333}.badge{font-weight:300}.progress{height:22px;padding:2px;background-color:#f6f6f6;border:1px solid #ccc;-webkit-box-shadow:none;box-shadow:none}.dropdown-menu{padding:0;margin-top:0;font-size:12px}.dropdown-menu>li>a{padding:12px 15px}.dropdown-header{padding-left:15px;padding-right:15px;font-size:9px;text-transform:uppercase}.popover{color:#fff;font-size:12px;font-weight:300}.panel-heading,.panel-footer{border-top-right-radius:0;border-top-left-radius:0}.panel-default .close{color:#222222}.modal .close{color:#222222}
diff --git a/modules/http/static/bootstrap.min.css.spdx b/modules/http/static/bootstrap.min.css.spdx
new file mode 100644
index 0000000..53aef61
--- /dev/null
+++ b/modules/http/static/bootstrap.min.css.spdx
@@ -0,0 +1,11 @@
+SPDXVersion: SPDX-2.1
+DataLicense: CC0-1.0
+SPDXID: SPDXRef-DOCUMENT
+DocumentName: bootswatch-yeti-bootstrap.min.css
+DocumentNamespace: http://spdx.org/spdxdocs/spdx-v2.1-da039738-1b9b-430a-984b-a97d1415ad0f
+
+PackageName: bootswatch-yeti-bootstrap.min.css
+PackageVersion: 3.3.6+2
+PackageDownloadLocation: git+https://github.com/twbs/bootstrap.git@a78dc3aed640a35914361b837ce24573a0515e19#yeti/bootstrap.min.css
+PackageOriginator: Person: Thomas Park (thomas@thomaspark.co)
+PackageLicenseDeclared: MIT
diff --git a/modules/http/static/bootstrap.min.js b/modules/http/static/bootstrap.min.js
new file mode 100644
index 0000000..07eaed1
--- /dev/null
+++ b/modules/http/static/bootstrap.min.js
@@ -0,0 +1,7 @@
+/*!
+ * Bootstrap v3.3.6 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * SPDX-License-Identifier: MIT
+ */
+if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>2)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 3")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.6",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.6",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.6",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.6",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.6",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&j<i.length-1&&j++,~j||(j=0),i.eq(j).trigger("focus")}}}};var h=a.fn.dropdown;a.fn.dropdown=d,a.fn.dropdown.Constructor=g,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=h,this},a(document).on("click.bs.dropdown.data-api",c).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",f,g.prototype.toggle).on("keydown.bs.dropdown.data-api",f,g.prototype.keydown).on("keydown.bs.dropdown.data-api",".dropdown-menu",g.prototype.keydown)}(jQuery),+function(a){"use strict";function b(b,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},c.DEFAULTS,e.data(),"object"==typeof b&&b);f||e.data("bs.modal",f=new c(this,g)),"string"==typeof b?f[b](d):g.show&&f.show(d)})}var c=function(b,c){this.options=c,this.$body=a(document.body),this.$element=a(b),this.$dialog=this.$element.find(".modal-dialog"),this.$backdrop=null,this.isShown=null,this.originalBodyPad=null,this.scrollbarWidth=0,this.ignoreBackdropClick=!1,this.options.remote&&this.$element.find(".modal-content").load(this.options.remote,a.proxy(function(){this.$element.trigger("loaded.bs.modal")},this))};c.VERSION="3.3.6",c.TRANSITION_DURATION=300,c.BACKDROP_TRANSITION_DURATION=150,c.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},c.prototype.toggle=function(a){return this.isShown?this.hide():this.show(a)},c.prototype.show=function(b){var d=this,e=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.checkScrollbar(),this.setScrollbar(),this.$body.addClass("modal-open"),this.escape(),this.resize(),this.$element.on("click.dismiss.bs.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.$dialog.on("mousedown.dismiss.bs.modal",function(){d.$element.one("mouseup.dismiss.bs.modal",function(b){a(b.target).is(d.$element)&&(d.ignoreBackdropClick=!0)})}),this.backdrop(function(){var e=a.support.transition&&d.$element.hasClass("fade");d.$element.parent().length||d.$element.appendTo(d.$body),d.$element.show().scrollTop(0),d.adjustDialog(),e&&d.$element[0].offsetWidth,d.$element.addClass("in"),d.enforceFocus();var f=a.Event("shown.bs.modal",{relatedTarget:b});e?d.$dialog.one("bsTransitionEnd",function(){d.$element.trigger("focus").trigger(f)}).emulateTransitionEnd(c.TRANSITION_DURATION):d.$element.trigger("focus").trigger(f)}))},c.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),this.resize(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").off("click.dismiss.bs.modal").off("mouseup.dismiss.bs.modal"),this.$dialog.off("mousedown.dismiss.bs.modal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",a.proxy(this.hideModal,this)).emulateTransitionEnd(c.TRANSITION_DURATION):this.hideModal())},c.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.trigger("focus")},this))},c.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keydown.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keydown.dismiss.bs.modal")},c.prototype.resize=function(){this.isShown?a(window).on("resize.bs.modal",a.proxy(this.handleUpdate,this)):a(window).off("resize.bs.modal")},c.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.$body.removeClass("modal-open"),a.resetAdjustments(),a.resetScrollbar(),a.$element.trigger("hidden.bs.modal")})},c.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},c.prototype.backdrop=function(b){var d=this,e=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var f=a.support.transition&&e;if(this.$backdrop=a(document.createElement("div")).addClass("modal-backdrop "+e).appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(a){return this.ignoreBackdropClick?void(this.ignoreBackdropClick=!1):void(a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus():this.hide()))},this)),f&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;f?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var g=function(){d.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",g).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):g()}else b&&b()},c.prototype.handleUpdate=function(){this.adjustDialog()},c.prototype.adjustDialog=function(){var a=this.$element[0].scrollHeight>document.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth<a,this.scrollbarWidth=this.measureScrollbar()},c.prototype.setScrollbar=function(){var a=parseInt(this.$body.css("padding-right")||0,10);this.originalBodyPad=document.body.style.paddingRight||"",this.bodyIsOverflowing&&this.$body.css("padding-right",a+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding-right",this.originalBodyPad)},c.prototype.measureScrollbar=function(){var a=document.createElement("div");a.className="modal-scrollbar-measure",this.$body.append(a);var b=a.offsetWidth-a.clientWidth;return this.$body[0].removeChild(a),b};var d=a.fn.modal;a.fn.modal=b,a.fn.modal.Constructor=c,a.fn.modal.noConflict=function(){return a.fn.modal=d,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(c){var d=a(this),e=d.attr("href"),f=a(d.attr("data-target")||e&&e.replace(/.*(?=#[^\s]+$)/,"")),g=f.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(e)&&e},f.data(),d.data());d.is("a")&&c.preventDefault(),f.one("show.bs.modal",function(a){a.isDefaultPrevented()||f.one("hidden.bs.modal",function(){d.is(":visible")&&d.trigger("focus")})}),b.call(f,g,this)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.tooltip",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.type=null,this.options=null,this.enabled=null,this.timeout=null,this.hoverState=null,this.$element=null,this.inState=null,this.init("tooltip",a,b)};c.VERSION="3.3.6",c.TRANSITION_DURATION=150,c.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-m<o.top?"bottom":"right"==h&&k.right+l>o.width?"left":"left"==h&&k.left-l<o.left?"right":h,f.removeClass(n).addClass(h)}var p=this.getCalculatedOffset(h,k,l,m);this.applyPlacement(p,h);var q=function(){var a=e.hoverState;e.$element.trigger("shown.bs."+e.type),e.hoverState=null,"out"==a&&e.leave(e)};a.support.transition&&this.$tip.hasClass("fade")?f.one("bsTransitionEnd",q).emulateTransitionEnd(c.TRANSITION_DURATION):q()}},c.prototype.applyPlacement=function(b,c){var d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight,g=parseInt(d.css("margin-top"),10),h=parseInt(d.css("margin-left"),10);isNaN(g)&&(g=0),isNaN(h)&&(h=0),b.top+=g,b.left+=h,a.offset.setOffset(d[0],a.extend({using:function(a){d.css({top:Math.round(a.top),left:Math.round(a.left)})}},b),0),d.addClass("in");var i=d[0].offsetWidth,j=d[0].offsetHeight;"top"==c&&j!=f&&(b.top=b.top+f-j);var k=this.getViewportAdjustedDelta(c,b,i,j);k.left?b.left+=k.left:b.top+=k.top;var l=/top|bottom/.test(c),m=l?2*k.left-e+i:2*k.top-f+j,n=l?"offsetWidth":"offsetHeight";d.offset(b),this.replaceArrow(m,d[0][n],l)},c.prototype.replaceArrow=function(a,b,c){this.arrow().css(c?"left":"top",50*(1-a/b)+"%").css(c?"top":"left","")},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},c.prototype.hide=function(b){function d(){"in"!=e.hoverState&&f.detach(),e.$element.removeAttr("aria-describedby").trigger("hidden.bs."+e.type),b&&b()}var e=this,f=a(this.$tip),g=a.Event("hide.bs."+this.type);return this.$element.trigger(g),g.isDefaultPrevented()?void 0:(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",d).emulateTransitionEnd(c.TRANSITION_DURATION):d(),this.hoverState=null,this)},c.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"string"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},c.prototype.hasContent=function(){return this.getTitle()},c.prototype.getPosition=function(b){b=b||this.$element;var c=b[0],d="BODY"==c.tagName,e=c.getBoundingClientRect();null==e.width&&(e=a.extend({},e,{width:e.right-e.left,height:e.bottom-e.top}));var f=d?{top:0,left:0}:b.offset(),g={scroll:d?document.documentElement.scrollTop||document.body.scrollTop:b.scrollTop()},h=d?{width:a(window).width(),height:a(window).height()}:null;return a.extend({},e,g,h,f)},c.prototype.getCalculatedOffset=function(a,b,c,d){return"bottom"==a?{top:b.top+b.height,left:b.left+b.width/2-c/2}:"top"==a?{top:b.top-d,left:b.left+b.width/2-c/2}:"left"==a?{top:b.top+b.height/2-d/2,left:b.left-c}:{top:b.top+b.height/2-d/2,left:b.left+b.width}},c.prototype.getViewportAdjustedDelta=function(a,b,c,d){var e={top:0,left:0};if(!this.$viewport)return e;var f=this.options.viewport&&this.options.viewport.padding||0,g=this.getPosition(this.$viewport);if(/right|left/.test(a)){var h=b.top-f-g.scroll,i=b.top+f-g.scroll+d;h<g.top?e.top=g.top-h:i>g.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;j<g.left?e.left=g.left-j:k>g.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.6",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.6",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b<e[0])return this.activeTarget=null,this.clear();for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(void 0===e[a+1]||b<e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,this.clear();var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");
+d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")},b.prototype.clear=function(){a(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.3.6",c.TRANSITION_DURATION=150,c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a"),f=a.Event("hide.bs.tab",{relatedTarget:b[0]}),g=a.Event("show.bs.tab",{relatedTarget:e[0]});if(e.trigger(f),b.trigger(g),!g.isDefaultPrevented()&&!f.isDefaultPrevented()){var h=a(d);this.activate(b.closest("li"),c),this.activate(h,h.parent(),function(){e.trigger({type:"hidden.bs.tab",relatedTarget:b[0]}),b.trigger({type:"shown.bs.tab",relatedTarget:e[0]})})}}},c.prototype.activate=function(b,d,e){function f(){g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.6",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery);
diff --git a/modules/http/static/bootstrap.min.js.spdx b/modules/http/static/bootstrap.min.js.spdx
new file mode 100644
index 0000000..d0df6eb
--- /dev/null
+++ b/modules/http/static/bootstrap.min.js.spdx
@@ -0,0 +1,11 @@
+SPDXVersion: SPDX-2.1
+DataLicense: CC0-1.0
+SPDXID: SPDXRef-DOCUMENT
+DocumentName: bootstrap.js
+DocumentNamespace: http://spdx.org/spdxdocs/spdx-v2.1-6797c679-d14a-4524-abe4-a668e07f213f
+
+PackageName: bootstrap.js
+PackageVersion: 3.3.6
+PackageDownloadLocation: git+https://github.com/twbs/bootstrap.git@81df608a40bf0629a1dc08e584849bb1e43e0b7a#dist/js/bootstrap.min.js
+PackageOriginator: Organization: Twitter
+PackageLicenseDeclared: MIT
diff --git a/modules/http/static/d3.js b/modules/http/static/d3.js
new file mode 100644
index 0000000..c3a27fd
--- /dev/null
+++ b/modules/http/static/d3.js
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+!function(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:0/0}function r(n){return null===n?0/0:+n}function u(n){return!isNaN(n)}function i(n){return{left:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)<0?r=i+1:u=i}return r},right:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)>0?u=i:r=i+1}return r}}}function o(n){return n.length}function a(n){for(var t=1;n*t%1;)t*=10;return t}function c(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function l(){this._=Object.create(null)}function s(n){return(n+="")===pa||n[0]===va?va+n:n}function f(n){return(n+="")[0]===va?n.slice(1):n}function h(n){return s(n)in this._}function g(n){return(n=s(n))in this._&&delete this._[n]}function p(){var n=[];for(var t in this._)n.push(f(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function m(){this._=Object.create(null)}function y(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=da.length;r>e;++e){var u=da[e]+t;if(u in n)return u}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,u=-1,i=r.length;++u<i;)(t=r[u].on)&&t.apply(this,arguments);return n}var e=[],r=new l;return t.on=function(t,u){var i,o=r.get(t);return arguments.length<2?o&&o.on:(o&&(o.on=null,e=e.slice(0,i=e.indexOf(o)).concat(e.slice(i+1)),r.remove(t)),u&&e.push(r.set(t,{on:u})),n)},t}function S(){ta.event.preventDefault()}function k(){for(var n,t=ta.event;n=t.sourceEvent;)t=n;return t}function E(n){for(var t=new _,e=0,r=arguments.length;++e<r;)t[arguments[e]]=w(t);return t.of=function(e,r){return function(u){try{var i=u.sourceEvent=ta.event;u.target=n,ta.event=u,t[u.type].apply(e,r)}finally{ta.event=i}}},t}function A(n){return ya(n,_a),n}function N(n){return"function"==typeof n?n:function(){return Ma(n,this)}}function C(n){return"function"==typeof n?n:function(){return xa(n,this)}}function z(n,t){function e(){this.removeAttribute(n)}function r(){this.removeAttributeNS(n.space,n.local)}function u(){this.setAttribute(n,t)}function i(){this.setAttributeNS(n.space,n.local,t)}function o(){var e=t.apply(this,arguments);null==e?this.removeAttribute(n):this.setAttribute(n,e)}function a(){var e=t.apply(this,arguments);null==e?this.removeAttributeNS(n.space,n.local):this.setAttributeNS(n.space,n.local,e)}return n=ta.ns.qualify(n),null==t?n.local?r:e:"function"==typeof t?n.local?a:o:n.local?i:u}function q(n){return n.trim().replace(/\s+/g," ")}function L(n){return new RegExp("(?:^|\\s+)"+ta.requote(n)+"(?:\\s+|$)","g")}function T(n){return(n+"").trim().split(/^|\s+/)}function R(n,t){function e(){for(var e=-1;++e<u;)n[e](this,t)}function r(){for(var e=-1,r=t.apply(this,arguments);++e<u;)n[e](this,r)}n=T(n).map(D);var u=n.length;return"function"==typeof t?r:e}function D(n){var t=L(n);return function(e,r){if(u=e.classList)return r?u.add(n):u.remove(n);var u=e.getAttribute("class")||"";r?(t.lastIndex=0,t.test(u)||e.setAttribute("class",q(u+" "+n))):e.setAttribute("class",q(u.replace(t," ")))}}function P(n,t,e){function r(){this.style.removeProperty(n)}function u(){this.style.setProperty(n,t,e)}function i(){var r=t.apply(this,arguments);null==r?this.style.removeProperty(n):this.style.setProperty(n,r,e)}return null==t?r:"function"==typeof t?i:u}function U(n,t){function e(){delete this[n]}function r(){this[n]=t}function u(){var e=t.apply(this,arguments);null==e?delete this[n]:this[n]=e}return null==t?e:"function"==typeof t?u:r}function j(n){function t(){var t=this.ownerDocument,e=this.namespaceURI;return e?t.createElementNS(e,n):t.createElement(n)}function e(){return this.ownerDocument.createElementNS(n.space,n.local)}return"function"==typeof n?n:(n=ta.ns.qualify(n)).local?e:t}function F(){var n=this.parentNode;n&&n.removeChild(this)}function H(n){return{__data__:n}}function O(n){return function(){return ba(this,n)}}function I(n){return arguments.length||(n=e),function(t,e){return t&&e?n(t.__data__,e.__data__):!t-!e}}function Y(n,t){for(var e=0,r=n.length;r>e;e++)for(var u,i=n[e],o=0,a=i.length;a>o;o++)(u=i[o])&&t(u,o,e);return n}function Z(n){return ya(n,Sa),n}function V(n){var t,e;return function(r,u,i){var o,a=n[i].update,c=a.length;for(i!=e&&(e=i,t=0),u>=t&&(t=u+1);!(o=a[t])&&++t<c;);return o}}function X(n,t,e){function r(){var t=this[o];t&&(this.removeEventListener(n,t,t.$),delete this[o])}function u(){var u=c(t,ra(arguments));r.call(this),this.addEventListener(n,this[o]=u,u.$=e),u._=t}function i(){var t,e=new RegExp("^__on([^.]+)"+ta.requote(n)+"$");for(var r in this)if(t=r.match(e)){var u=this[r];this.removeEventListener(t[1],u,u.$),delete this[r]}}var o="__on"+n,a=n.indexOf("."),c=$;a>0&&(n=n.slice(0,a));var l=ka.get(n);return l&&(n=l,c=B),a?t?u:r:t?b:i}function $(n,t){return function(e){var r=ta.event;ta.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{ta.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Aa,u="click"+r,i=ta.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==Ea&&(Ea="onselectstart"in e?!1:x(e.style,"userSelect")),Ea){var o=n(e).style,a=o[Ea];o[Ea]="none"}return function(n){if(i.on(r,null),Ea&&(o[Ea]=a),n){var t=function(){i.on(u,null)};i.on(u,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var u=r.createSVGPoint();if(0>Na){var i=t(n);if(i.scrollX||i.scrollY){r=ta.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var o=r[0][0].getScreenCTM();Na=!(o.f||o.e),r.remove()}}return Na?(u.x=e.pageX,u.y=e.pageY):(u.x=e.clientX,u.y=e.clientY),u=u.matrixTransform(n.getScreenCTM().inverse()),[u.x,u.y]}var a=n.getBoundingClientRect();return[e.clientX-a.left-n.clientLeft,e.clientY-a.top-n.clientTop]}function G(){return ta.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nt(n){return n>1?0:-1>n?qa:Math.acos(n)}function tt(n){return n>1?Ra:-1>n?-Ra:Math.asin(n)}function et(n){return((n=Math.exp(n))-1/n)/2}function rt(n){return((n=Math.exp(n))+1/n)/2}function ut(n){return((n=Math.exp(2*n))-1)/(n+1)}function it(n){return(n=Math.sin(n/2))*n}function ot(){}function at(n,t,e){return this instanceof at?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof at?new at(n.h,n.s,n.l):bt(""+n,_t,at):new at(n,t,e)}function ct(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?i+(o-i)*n/60:180>n?o:240>n?i+(o-i)*(240-n)/60:i}function u(n){return Math.round(255*r(n))}var i,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,i=2*e-o,new mt(u(n+120),u(n),u(n-120))}function lt(n,t,e){return this instanceof lt?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof lt?new lt(n.h,n.c,n.l):n instanceof ft?gt(n.l,n.a,n.b):gt((n=wt((n=ta.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new lt(n,t,e)}function st(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new ft(e,Math.cos(n*=Da)*t,Math.sin(n)*t)}function ft(n,t,e){return this instanceof ft?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof ft?new ft(n.l,n.a,n.b):n instanceof lt?st(n.h,n.c,n.l):wt((n=mt(n)).r,n.g,n.b):new ft(n,t,e)}function ht(n,t,e){var r=(n+16)/116,u=r+t/500,i=r-e/200;return u=pt(u)*Xa,r=pt(r)*$a,i=pt(i)*Ba,new mt(dt(3.2404542*u-1.5371385*r-.4985314*i),dt(-.969266*u+1.8760108*r+.041556*i),dt(.0556434*u-.2040259*r+1.0572252*i))}function gt(n,t,e){return n>0?new lt(Math.atan2(e,t)*Pa,Math.sqrt(t*t+e*e),n):new lt(0/0,0/0,n)}function pt(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function vt(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function dt(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function mt(n,t,e){return this instanceof mt?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof mt?new mt(n.r,n.g,n.b):bt(""+n,mt,ct):new mt(n,t,e)}function yt(n){return new mt(n>>16,n>>8&255,255&n)}function Mt(n){return yt(n)+""}function xt(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function bt(n,t,e){var r,u,i,o=0,a=0,c=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(u=r[2].split(","),r[1]){case"hsl":return e(parseFloat(u[0]),parseFloat(u[1])/100,parseFloat(u[2])/100);case"rgb":return t(kt(u[0]),kt(u[1]),kt(u[2]))}return(i=Ga.get(n))?t(i.r,i.g,i.b):(null==n||"#"!==n.charAt(0)||isNaN(i=parseInt(n.slice(1),16))||(4===n.length?(o=(3840&i)>>4,o=o>>4|o,a=240&i,a=a>>4|a,c=15&i,c=c<<4|c):7===n.length&&(o=(16711680&i)>>16,a=(65280&i)>>8,c=255&i)),t(o,a,c))}function _t(n,t,e){var r,u,i=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-i,c=(o+i)/2;return a?(u=.5>c?a/(o+i):a/(2-o-i),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=0/0,u=c>0&&1>c?0:r),new at(r,u,c)}function wt(n,t,e){n=St(n),t=St(t),e=St(e);var r=vt((.4124564*n+.3575761*t+.1804375*e)/Xa),u=vt((.2126729*n+.7151522*t+.072175*e)/$a),i=vt((.0193339*n+.119192*t+.9503041*e)/Ba);return ft(116*u-16,500*(r-u),200*(u-i))}function St(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function kt(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function Et(n){return"function"==typeof n?n:function(){return n}}function At(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Nt(t,e,n,r)}}function Nt(n,t,e,r){function u(){var n,t=c.status;if(!t&&zt(c)||t>=200&&300>t||304===t){try{n=e.call(i,c)}catch(r){return void o.error.call(i,r)}o.load.call(i,n)}else o.error.call(i,c)}var i={},o=ta.dispatch("beforesend","progress","load","error"),a={},c=new XMLHttpRequest,l=null;return!this.XDomainRequest||"withCredentials"in c||!/^(http(s)?:)?\/\//.test(n)||(c=new XDomainRequest),"onload"in c?c.onload=c.onerror=u:c.onreadystatechange=function(){c.readyState>3&&u()},c.onprogress=function(n){var t=ta.event;ta.event=n;try{o.progress.call(i,c)}finally{ta.event=t}},i.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",i)},i.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",i):t},i.responseType=function(n){return arguments.length?(l=n,i):l},i.response=function(n){return e=n,i},["get","post"].forEach(function(n){i[n]=function(){return i.send.apply(i,[n].concat(ra(arguments)))}}),i.send=function(e,r,u){if(2===arguments.length&&"function"==typeof r&&(u=r,r=null),c.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),c.setRequestHeader)for(var s in a)c.setRequestHeader(s,a[s]);return null!=t&&c.overrideMimeType&&c.overrideMimeType(t),null!=l&&(c.responseType=l),null!=u&&i.on("error",u).on("load",function(n){u(null,n)}),o.beforesend.call(i,c),c.send(null==r?null:r),i},i.abort=function(){return c.abort(),i},ta.rebind(i,o,"on"),null==r?i:i.get(Ct(r))}function Ct(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function zt(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qt(){var n=Lt(),t=Tt()-n;t>24?(isFinite(t)&&(clearTimeout(tc),tc=setTimeout(qt,t)),nc=0):(nc=1,rc(qt))}function Lt(){var n=Date.now();for(ec=Ka;ec;)n>=ec.t&&(ec.f=ec.c(n-ec.t)),ec=ec.n;return n}function Tt(){for(var n,t=Ka,e=1/0;t;)t.f?t=n?n.n=t.n:Ka=t.n:(t.t<e&&(e=t.t),t=(n=t).n);return Qa=n,e}function Rt(n,t){return t-(n?Math.ceil(Math.log(n)/Math.LN10):1)}function Dt(n,t){var e=Math.pow(10,3*ga(8-t));return{scale:t>8?function(n){return n/e}:function(n){return n*e},symbol:n}}function Pt(n){var t=n.decimal,e=n.thousands,r=n.grouping,u=n.currency,i=r&&e?function(n,t){for(var u=n.length,i=[],o=0,a=r[0],c=0;u>0&&a>0&&(c+a+1>t&&(a=Math.max(1,t-c)),i.push(n.substring(u-=a,u+a)),!((c+=a+1)>t));)a=r[o=(o+1)%r.length];return i.reverse().join(e)}:y;return function(n){var e=ic.exec(n),r=e[1]||" ",o=e[2]||">",a=e[3]||"-",c=e[4]||"",l=e[5],s=+e[6],f=e[7],h=e[8],g=e[9],p=1,v="",d="",m=!1,y=!0;switch(h&&(h=+h.substring(1)),(l||"0"===r&&"="===o)&&(l=r="0",o="="),g){case"n":f=!0,g="g";break;case"%":p=100,d="%",g="f";break;case"p":p=100,d="%",g="r";break;case"b":case"o":case"x":case"X":"#"===c&&(v="0"+g.toLowerCase());case"c":y=!1;case"d":m=!0,h=0;break;case"s":p=-1,g="r"}"$"===c&&(v=u[0],d=u[1]),"r"!=g||h||(g="g"),null!=h&&("g"==g?h=Math.max(1,Math.min(21,h)):("e"==g||"f"==g)&&(h=Math.max(0,Math.min(20,h)))),g=oc.get(g)||Ut;var M=l&&f;return function(n){var e=d;if(m&&n%1)return"";var u=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===a?"":a;if(0>p){var c=ta.formatPrefix(n,h);n=c.scale(n),e=c.symbol+d}else n*=p;n=g(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=y?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!l&&f&&(x=i(x,1/0));var S=v.length+x.length+b.length+(M?0:u.length),k=s>S?new Array(S=s-S+1).join(r):"";return M&&(x=i(k+x,k.length?s-b.length:1/0)),u+=v,n=x+b,("<"===o?u+n+k:">"===o?k+u+n:"^"===o?k.substring(0,S>>=1)+u+n+k.substring(S):u+(M?n:k+n))+e}}}function Ut(n){return n+""}function jt(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function Ft(n,t,e){function r(t){var e=n(t),r=i(e,1);return r-t>t-e?e:r}function u(e){return t(e=n(new cc(e-1)),1),e}function i(n,e){return t(n=new cc(+n),e),n}function o(n,r,i){var o=u(n),a=[];if(i>1)for(;r>o;)e(o)%i||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{cc=jt;var r=new jt;return r._=n,o(r,t,e)}finally{cc=Date}}n.floor=n,n.round=r,n.ceil=u,n.offset=i,n.range=o;var c=n.utc=Ht(n);return c.floor=c,c.round=Ht(r),c.ceil=Ht(u),c.offset=Ht(i),c.range=a,n}function Ht(n){return function(t,e){try{cc=jt;var r=new jt;return r._=t,n(r,e)._}finally{cc=Date}}}function Ot(n){function t(n){function t(t){for(var e,u,i,o=[],a=-1,c=0;++a<r;)37===n.charCodeAt(a)&&(o.push(n.slice(c,a)),null!=(u=sc[e=n.charAt(++a)])&&(e=n.charAt(++a)),(i=N[e])&&(e=i(t,null==u?"e"===e?" ":"0":u)),o.push(e),c=a+1);return o.push(n.slice(c,a)),o.join("")}var r=n.length;return t.parse=function(t){var r={y:1900,m:0,d:1,H:0,M:0,S:0,L:0,Z:null},u=e(r,n,t,0);if(u!=t.length)return null;"p"in r&&(r.H=r.H%12+12*r.p);var i=null!=r.Z&&cc!==jt,o=new(i?jt:cc);return"j"in r?o.setFullYear(r.y,0,r.j):"w"in r&&("W"in r||"U"in r)?(o.setFullYear(r.y,0,1),o.setFullYear(r.y,0,"W"in r?(r.w+6)%7+7*r.W-(o.getDay()+5)%7:r.w+7*r.U-(o.getDay()+6)%7)):o.setFullYear(r.y,r.m,r.d),o.setHours(r.H+(r.Z/100|0),r.M+r.Z%100,r.S,r.L),i?o._:o},t.toString=function(){return n},t}function e(n,t,e,r){for(var u,i,o,a=0,c=t.length,l=e.length;c>a;){if(r>=l)return-1;if(u=t.charCodeAt(a++),37===u){if(o=t.charAt(a++),i=C[o in sc?t.charAt(a++):o],!i||(r=i(n,e,r))<0)return-1}else if(u!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){E.lastIndex=0;var r=E.exec(t.slice(e));return r?(n.m=A.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,r){return e(n,N.c.toString(),t,r)}function c(n,t,r){return e(n,N.x.toString(),t,r)}function l(n,t,r){return e(n,N.X.toString(),t,r)}function s(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var f=n.dateTime,h=n.date,g=n.time,p=n.periods,v=n.days,d=n.shortDays,m=n.months,y=n.shortMonths;t.utc=function(n){function e(n){try{cc=jt;var t=new cc;return t._=n,r(t)}finally{cc=Date}}var r=t(n);return e.parse=function(n){try{cc=jt;var t=r.parse(n);return t&&t._}finally{cc=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ae;var M=ta.map(),x=Yt(v),b=Zt(v),_=Yt(d),w=Zt(d),S=Yt(m),k=Zt(m),E=Yt(y),A=Zt(y);p.forEach(function(n,t){M.set(n.toLowerCase(),t)});var N={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return y[n.getMonth()]},B:function(n){return m[n.getMonth()]},c:t(f),d:function(n,t){return It(n.getDate(),t,2)},e:function(n,t){return It(n.getDate(),t,2)},H:function(n,t){return It(n.getHours(),t,2)},I:function(n,t){return It(n.getHours()%12||12,t,2)},j:function(n,t){return It(1+ac.dayOfYear(n),t,3)},L:function(n,t){return It(n.getMilliseconds(),t,3)},m:function(n,t){return It(n.getMonth()+1,t,2)},M:function(n,t){return It(n.getMinutes(),t,2)},p:function(n){return p[+(n.getHours()>=12)]},S:function(n,t){return It(n.getSeconds(),t,2)},U:function(n,t){return It(ac.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return It(ac.mondayOfYear(n),t,2)},x:t(h),X:t(g),y:function(n,t){return It(n.getFullYear()%100,t,2)},Y:function(n,t){return It(n.getFullYear()%1e4,t,4)},Z:ie,"%":function(){return"%"}},C={a:r,A:u,b:i,B:o,c:a,d:Qt,e:Qt,H:te,I:te,j:ne,L:ue,m:Kt,M:ee,p:s,S:re,U:Xt,w:Vt,W:$t,x:c,X:l,y:Wt,Y:Bt,Z:Jt,"%":oe};return t}function It(n,t,e){var r=0>n?"-":"",u=(r?-n:n)+"",i=u.length;return r+(e>i?new Array(e-i+1).join(t)+u:u)}function Yt(n){return new RegExp("^(?:"+n.map(ta.requote).join("|")+")","i")}function Zt(n){for(var t=new l,e=-1,r=n.length;++e<r;)t.set(n[e].toLowerCase(),e);return t}function Vt(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+1));return r?(n.w=+r[0],e+r[0].length):-1}function Xt(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e));return r?(n.U=+r[0],e+r[0].length):-1}function $t(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e));return r?(n.W=+r[0],e+r[0].length):-1}function Bt(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+4));return r?(n.y=+r[0],e+r[0].length):-1}function Wt(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+2));return r?(n.y=Gt(+r[0]),e+r[0].length):-1}function Jt(n,t,e){return/^[+-]\d{4}$/.test(t=t.slice(e,e+5))?(n.Z=-t,e+5):-1}function Gt(n){return n+(n>68?1900:2e3)}function Kt(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function Qt(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function ne(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function te(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function ee(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function re(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ue(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function ie(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=ga(t)/60|0,u=ga(t)%60;return e+It(r,"0",2)+It(u,"0",2)}function oe(n,t,e){hc.lastIndex=0;var r=hc.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ae(n){for(var t=n.length,e=-1;++e<t;)n[e][0]=this(n[e][0]);return function(t){for(var e=0,r=n[e];!r[1](t);)r=n[++e];return r[0](t)}}function ce(){}function le(n,t,e){var r=e.s=n+t,u=r-n,i=r-u;e.t=n-i+(t-u)}function se(n,t){n&&dc.hasOwnProperty(n.type)&&dc[n.type](n,t)}function fe(n,t,e){var r,u=-1,i=n.length-e;for(t.lineStart();++u<i;)r=n[u],t.point(r[0],r[1],r[2]);t.lineEnd()}function he(n,t){var e=-1,r=n.length;for(t.polygonStart();++e<r;)fe(n[e],t,1);t.polygonEnd()}function ge(){function n(n,t){n*=Da,t=t*Da/2+qa/4;var e=n-r,o=e>=0?1:-1,a=o*e,c=Math.cos(t),l=Math.sin(t),s=i*l,f=u*c+s*Math.cos(a),h=s*o*Math.sin(a);yc.add(Math.atan2(h,f)),r=n,u=c,i=l}var t,e,r,u,i;Mc.point=function(o,a){Mc.point=n,r=(t=o)*Da,u=Math.cos(a=(e=a)*Da/2+qa/4),i=Math.sin(a)},Mc.lineEnd=function(){n(t,e)}}function pe(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function ve(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function de(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function me(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function ye(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function Me(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function xe(n){return[Math.atan2(n[1],n[0]),tt(n[2])]}function be(n,t){return ga(n[0]-t[0])<Ca&&ga(n[1]-t[1])<Ca}function _e(n,t){n*=Da;var e=Math.cos(t*=Da);we(e*Math.cos(n),e*Math.sin(n),Math.sin(t))}function we(n,t,e){++xc,_c+=(n-_c)/xc,wc+=(t-wc)/xc,Sc+=(e-Sc)/xc}function Se(){function n(n,u){n*=Da;var i=Math.cos(u*=Da),o=i*Math.cos(n),a=i*Math.sin(n),c=Math.sin(u),l=Math.atan2(Math.sqrt((l=e*c-r*a)*l+(l=r*o-t*c)*l+(l=t*a-e*o)*l),t*o+e*a+r*c);bc+=l,kc+=l*(t+(t=o)),Ec+=l*(e+(e=a)),Ac+=l*(r+(r=c)),we(t,e,r)}var t,e,r;qc.point=function(u,i){u*=Da;var o=Math.cos(i*=Da);t=o*Math.cos(u),e=o*Math.sin(u),r=Math.sin(i),qc.point=n,we(t,e,r)}}function ke(){qc.point=_e}function Ee(){function n(n,t){n*=Da;var e=Math.cos(t*=Da),o=e*Math.cos(n),a=e*Math.sin(n),c=Math.sin(t),l=u*c-i*a,s=i*o-r*c,f=r*a-u*o,h=Math.sqrt(l*l+s*s+f*f),g=r*o+u*a+i*c,p=h&&-nt(g)/h,v=Math.atan2(h,g);Nc+=p*l,Cc+=p*s,zc+=p*f,bc+=v,kc+=v*(r+(r=o)),Ec+=v*(u+(u=a)),Ac+=v*(i+(i=c)),we(r,u,i)}var t,e,r,u,i;qc.point=function(o,a){t=o,e=a,qc.point=n,o*=Da;var c=Math.cos(a*=Da);r=c*Math.cos(o),u=c*Math.sin(o),i=Math.sin(a),we(r,u,i)},qc.lineEnd=function(){n(t,e),qc.lineEnd=ke,qc.point=_e}}function Ae(n,t){function e(e,r){return e=n(e,r),t(e[0],e[1])}return n.invert&&t.invert&&(e.invert=function(e,r){return e=t.invert(e,r),e&&n.invert(e[0],e[1])}),e}function Ne(){return!0}function Ce(n,t,e,r,u){var i=[],o=[];if(n.forEach(function(n){if(!((t=n.length-1)<=0)){var t,e=n[0],r=n[t];if(be(e,r)){u.lineStart();for(var a=0;t>a;++a)u.point((e=n[a])[0],e[1]);return void u.lineEnd()}var c=new qe(e,n,null,!0),l=new qe(e,null,c,!1);c.o=l,i.push(c),o.push(l),c=new qe(r,n,null,!1),l=new qe(r,null,c,!0),c.o=l,i.push(c),o.push(l)}}),o.sort(t),ze(i),ze(o),i.length){for(var a=0,c=e,l=o.length;l>a;++a)o[a].e=c=!c;for(var s,f,h=i[0];;){for(var g=h,p=!0;g.v;)if((g=g.n)===h)return;s=g.z,u.lineStart();do{if(g.v=g.o.v=!0,g.e){if(p)for(var a=0,l=s.length;l>a;++a)u.point((f=s[a])[0],f[1]);else r(g.x,g.n.x,1,u);g=g.n}else{if(p){s=g.p.z;for(var a=s.length-1;a>=0;--a)u.point((f=s[a])[0],f[1])}else r(g.x,g.p.x,-1,u);g=g.p}g=g.o,s=g.z,p=!p}while(!g.v);u.lineEnd()}}}function ze(n){if(t=n.length){for(var t,e,r=0,u=n[0];++r<t;)u.n=e=n[r],e.p=u,u=e;u.n=e=n[0],e.p=u}}function qe(n,t,e,r){this.x=n,this.z=t,this.o=e,this.e=r,this.v=!1,this.n=this.p=null}function Le(n,t,e,r){return function(u,i){function o(t,e){var r=u(t,e);n(t=r[0],e=r[1])&&i.point(t,e)}function a(n,t){var e=u(n,t);d.point(e[0],e[1])}function c(){y.point=a,d.lineStart()}function l(){y.point=o,d.lineEnd()}function s(n,t){v.push([n,t]);var e=u(n,t);x.point(e[0],e[1])}function f(){x.lineStart(),v=[]}function h(){s(v[0][0],v[0][1]),x.lineEnd();var n,t=x.clean(),e=M.buffer(),r=e.length;if(v.pop(),p.push(v),v=null,r)if(1&t){n=e[0];var u,r=n.length-1,o=-1;if(r>0){for(b||(i.polygonStart(),b=!0),i.lineStart();++o<r;)i.point((u=n[o])[0],u[1]);i.lineEnd()}}else r>1&&2&t&&e.push(e.pop().concat(e.shift())),g.push(e.filter(Te))}var g,p,v,d=t(i),m=u.invert(r[0],r[1]),y={point:o,lineStart:c,lineEnd:l,polygonStart:function(){y.point=s,y.lineStart=f,y.lineEnd=h,g=[],p=[]},polygonEnd:function(){y.point=o,y.lineStart=c,y.lineEnd=l,g=ta.merge(g);var n=Fe(m,p);g.length?(b||(i.polygonStart(),b=!0),Ce(g,De,n,e,i)):n&&(b||(i.polygonStart(),b=!0),i.lineStart(),e(null,null,1,i),i.lineEnd()),b&&(i.polygonEnd(),b=!1),g=p=null},sphere:function(){i.polygonStart(),i.lineStart(),e(null,null,1,i),i.lineEnd(),i.polygonEnd()}},M=Re(),x=t(M),b=!1;return y}}function Te(n){return n.length>1}function Re(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function De(n,t){return((n=n.x)[0]<0?n[1]-Ra-Ca:Ra-n[1])-((t=t.x)[0]<0?t[1]-Ra-Ca:Ra-t[1])}function Pe(n){var t,e=0/0,r=0/0,u=0/0;return{lineStart:function(){n.lineStart(),t=1},point:function(i,o){var a=i>0?qa:-qa,c=ga(i-e);ga(c-qa)<Ca?(n.point(e,r=(r+o)/2>0?Ra:-Ra),n.point(u,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(i,r),t=0):u!==a&&c>=qa&&(ga(e-u)<Ca&&(e-=u*Ca),ga(i-a)<Ca&&(i-=a*Ca),r=Ue(e,r,i,o),n.point(u,r),n.lineEnd(),n.lineStart(),n.point(a,r),t=0),n.point(e=i,r=o),u=a},lineEnd:function(){n.lineEnd(),e=r=0/0},clean:function(){return 2-t}}}function Ue(n,t,e,r){var u,i,o=Math.sin(n-e);return ga(o)>Ca?Math.atan((Math.sin(t)*(i=Math.cos(r))*Math.sin(e)-Math.sin(r)*(u=Math.cos(t))*Math.sin(n))/(u*i*o)):(t+r)/2}function je(n,t,e,r){var u;if(null==n)u=e*Ra,r.point(-qa,u),r.point(0,u),r.point(qa,u),r.point(qa,0),r.point(qa,-u),r.point(0,-u),r.point(-qa,-u),r.point(-qa,0),r.point(-qa,u);else if(ga(n[0]-t[0])>Ca){var i=n[0]<t[0]?qa:-qa;u=e*i/2,r.point(-i,u),r.point(0,u),r.point(i,u)}else r.point(t[0],t[1])}function Fe(n,t){var e=n[0],r=n[1],u=[Math.sin(e),-Math.cos(e),0],i=0,o=0;yc.reset();for(var a=0,c=t.length;c>a;++a){var l=t[a],s=l.length;if(s)for(var f=l[0],h=f[0],g=f[1]/2+qa/4,p=Math.sin(g),v=Math.cos(g),d=1;;){d===s&&(d=0),n=l[d];var m=n[0],y=n[1]/2+qa/4,M=Math.sin(y),x=Math.cos(y),b=m-h,_=b>=0?1:-1,w=_*b,S=w>qa,k=p*M;if(yc.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),i+=S?b+_*La:b,S^h>=e^m>=e){var E=de(pe(f),pe(n));Me(E);var A=de(u,E);Me(A);var N=(S^b>=0?-1:1)*tt(A[2]);(r>N||r===N&&(E[0]||E[1]))&&(o+=S^b>=0?1:-1)}if(!d++)break;h=m,p=M,v=x,f=n}}return(-Ca>i||Ca>i&&0>yc)^1&o}function He(n){function t(n,t){return Math.cos(n)*Math.cos(t)>i}function e(n){var e,i,c,l,s;return{lineStart:function(){l=c=!1,s=1},point:function(f,h){var g,p=[f,h],v=t(f,h),d=o?v?0:u(f,h):v?u(f+(0>f?qa:-qa),h):0;if(!e&&(l=c=v)&&n.lineStart(),v!==c&&(g=r(e,p),(be(e,g)||be(p,g))&&(p[0]+=Ca,p[1]+=Ca,v=t(p[0],p[1]))),v!==c)s=0,v?(n.lineStart(),g=r(p,e),n.point(g[0],g[1])):(g=r(e,p),n.point(g[0],g[1]),n.lineEnd()),e=g;else if(a&&e&&o^v){var m;d&i||!(m=r(p,e,!0))||(s=0,o?(n.lineStart(),n.point(m[0][0],m[0][1]),n.point(m[1][0],m[1][1]),n.lineEnd()):(n.point(m[1][0],m[1][1]),n.lineEnd(),n.lineStart(),n.point(m[0][0],m[0][1])))}!v||e&&be(e,p)||n.point(p[0],p[1]),e=p,c=v,i=d},lineEnd:function(){c&&n.lineEnd(),e=null},clean:function(){return s|(l&&c)<<1}}}function r(n,t,e){var r=pe(n),u=pe(t),o=[1,0,0],a=de(r,u),c=ve(a,a),l=a[0],s=c-l*l;if(!s)return!e&&n;var f=i*c/s,h=-i*l/s,g=de(o,a),p=ye(o,f),v=ye(a,h);me(p,v);var d=g,m=ve(p,d),y=ve(d,d),M=m*m-y*(ve(p,p)-1);if(!(0>M)){var x=Math.sqrt(M),b=ye(d,(-m-x)/y);if(me(b,p),b=xe(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],E=t[1];w>S&&(_=w,w=S,S=_);var A=S-w,N=ga(A-qa)<Ca,C=N||Ca>A;if(!N&&k>E&&(_=k,k=E,E=_),C?N?k+E>0^b[1]<(ga(b[0]-w)<Ca?k:E):k<=b[1]&&b[1]<=E:A>qa^(w<=b[0]&&b[0]<=S)){var z=ye(d,(-m+x)/y);return me(z,p),[b,xe(z)]}}}function u(t,e){var r=o?n:qa-n,u=0;return-r>t?u|=1:t>r&&(u|=2),-r>e?u|=4:e>r&&(u|=8),u}var i=Math.cos(n),o=i>0,a=ga(i)>Ca,c=gr(n,6*Da);return Le(t,e,c,o?[0,-n]:[-qa,n-qa])}function Oe(n,t,e,r){return function(u){var i,o=u.a,a=u.b,c=o.x,l=o.y,s=a.x,f=a.y,h=0,g=1,p=s-c,v=f-l;if(i=n-c,p||!(i>0)){if(i/=p,0>p){if(h>i)return;g>i&&(g=i)}else if(p>0){if(i>g)return;i>h&&(h=i)}if(i=e-c,p||!(0>i)){if(i/=p,0>p){if(i>g)return;i>h&&(h=i)}else if(p>0){if(h>i)return;g>i&&(g=i)}if(i=t-l,v||!(i>0)){if(i/=v,0>v){if(h>i)return;g>i&&(g=i)}else if(v>0){if(i>g)return;i>h&&(h=i)}if(i=r-l,v||!(0>i)){if(i/=v,0>v){if(i>g)return;i>h&&(h=i)}else if(v>0){if(h>i)return;g>i&&(g=i)}return h>0&&(u.a={x:c+h*p,y:l+h*v}),1>g&&(u.b={x:c+g*p,y:l+g*v}),u}}}}}}function Ie(n,t,e,r){function u(r,u){return ga(r[0]-n)<Ca?u>0?0:3:ga(r[0]-e)<Ca?u>0?2:1:ga(r[1]-t)<Ca?u>0?1:0:u>0?3:2}function i(n,t){return o(n.x,t.x)}function o(n,t){var e=u(n,1),r=u(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function c(n){for(var t=0,e=d.length,r=n[1],u=0;e>u;++u)for(var i,o=1,a=d[u],c=a.length,l=a[0];c>o;++o)i=a[o],l[1]<=r?i[1]>r&&Q(l,i,n)>0&&++t:i[1]<=r&&Q(l,i,n)<0&&--t,l=i;return 0!==t}function l(i,a,c,l){var s=0,f=0;if(null==i||(s=u(i,c))!==(f=u(a,c))||o(i,a)<0^c>0){do l.point(0===s||3===s?n:e,s>1?r:t);while((s=(s+c+4)%4)!==f)}else l.point(a[0],a[1])}function s(u,i){return u>=n&&e>=u&&i>=t&&r>=i}function f(n,t){s(n,t)&&a.point(n,t)}function h(){C.point=p,d&&d.push(m=[]),S=!0,w=!1,b=_=0/0}function g(){v&&(p(y,M),x&&w&&A.rejoin(),v.push(A.buffer())),C.point=f,w&&a.lineEnd()}function p(n,t){n=Math.max(-Tc,Math.min(Tc,n)),t=Math.max(-Tc,Math.min(Tc,t));var e=s(n,t);if(d&&m.push([n,t]),S)y=n,M=t,x=e,S=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&w)a.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};N(r)?(w||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),k=!1):e&&(a.lineStart(),a.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,m,y,M,x,b,_,w,S,k,E=a,A=Re(),N=Oe(n,t,e,r),C={point:f,lineStart:h,lineEnd:g,polygonStart:function(){a=A,v=[],d=[],k=!0},polygonEnd:function(){a=E,v=ta.merge(v);var t=c([n,r]),e=k&&t,u=v.length;(e||u)&&(a.polygonStart(),e&&(a.lineStart(),l(null,null,1,a),a.lineEnd()),u&&Ce(v,i,t,l,a),a.polygonEnd()),v=d=m=null}};return C}}function Ye(n){var t=0,e=qa/3,r=ir(n),u=r(t,e);return u.parallels=function(n){return arguments.length?r(t=n[0]*qa/180,e=n[1]*qa/180):[t/qa*180,e/qa*180]},u}function Ze(n,t){function e(n,t){var e=Math.sqrt(i-2*u*Math.sin(t))/u;return[e*Math.sin(n*=u),o-e*Math.cos(n)]}var r=Math.sin(n),u=(r+Math.sin(t))/2,i=1+r*(2*u-r),o=Math.sqrt(i)/u;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/u,tt((i-(n*n+e*e)*u*u)/(2*u))]},e}function Ve(){function n(n,t){Dc+=u*n-r*t,r=n,u=t}var t,e,r,u;Hc.point=function(i,o){Hc.point=n,t=r=i,e=u=o},Hc.lineEnd=function(){n(t,e)}}function Xe(n,t){Pc>n&&(Pc=n),n>jc&&(jc=n),Uc>t&&(Uc=t),t>Fc&&(Fc=t)}function $e(){function n(n,t){o.push("M",n,",",t,i)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function u(){o.push("Z")}var i=Be(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return i=Be(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function Be(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function We(n,t){_c+=n,wc+=t,++Sc}function Je(){function n(n,r){var u=n-t,i=r-e,o=Math.sqrt(u*u+i*i);kc+=o*(t+n)/2,Ec+=o*(e+r)/2,Ac+=o,We(t=n,e=r)}var t,e;Ic.point=function(r,u){Ic.point=n,We(t=r,e=u)}}function Ge(){Ic.point=We}function Ke(){function n(n,t){var e=n-r,i=t-u,o=Math.sqrt(e*e+i*i);kc+=o*(r+n)/2,Ec+=o*(u+t)/2,Ac+=o,o=u*n-r*t,Nc+=o*(r+n),Cc+=o*(u+t),zc+=3*o,We(r=n,u=t)}var t,e,r,u;Ic.point=function(i,o){Ic.point=n,We(t=r=i,e=u=o)},Ic.lineEnd=function(){n(t,e)}}function Qe(n){function t(t,e){n.moveTo(t+o,e),n.arc(t,e,o,0,La)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function u(){a.point=t}function i(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:u,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=u,a.point=t},pointRadius:function(n){return o=n,a},result:b};return a}function nr(n){function t(n){return(a?r:e)(n)}function e(t){return rr(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=0/0,S.point=i,t.lineStart()}function i(e,r){var i=pe([e,r]),o=n(e,r);u(M,x,y,b,_,w,M=o[0],x=o[1],y=e,b=i[0],_=i[1],w=i[2],a,t),t.point(M,x)}function o(){S.point=e,t.lineEnd()}function c(){r(),S.point=l,S.lineEnd=s}function l(n,t){i(f=n,h=t),g=M,p=x,v=b,d=_,m=w,S.point=i}function s(){u(M,x,y,b,_,w,g,p,f,v,d,m,a,t),S.lineEnd=o,o()}var f,h,g,p,v,d,m,y,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=c
+},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function u(t,e,r,a,c,l,s,f,h,g,p,v,d,m){var y=s-t,M=f-e,x=y*y+M*M;if(x>4*i&&d--){var b=a+g,_=c+p,w=l+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),E=ga(ga(w)-1)<Ca||ga(r-h)<Ca?(r+h)/2:Math.atan2(_,b),A=n(E,k),N=A[0],C=A[1],z=N-t,q=C-e,L=M*z-y*q;(L*L/x>i||ga((y*z+M*q)/x-.5)>.3||o>a*g+c*p+l*v)&&(u(t,e,r,a,c,l,N,C,E,b/=S,_/=S,w,d,m),m.point(N,C),u(N,C,E,b,_,w,s,f,h,g,p,v,d,m))}}var i=.5,o=Math.cos(30*Da),a=16;return t.precision=function(n){return arguments.length?(a=(i=n*n)>0&&16,t):Math.sqrt(i)},t}function tr(n){var t=nr(function(t,e){return n([t*Pa,e*Pa])});return function(n){return or(t(n))}}function er(n){this.stream=n}function rr(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function ur(n){return ir(function(){return n})()}function ir(n){function t(n){return n=a(n[0]*Da,n[1]*Da),[n[0]*h+c,l-n[1]*h]}function e(n){return n=a.invert((n[0]-c)/h,(l-n[1])/h),n&&[n[0]*Pa,n[1]*Pa]}function r(){a=Ae(o=lr(m,M,x),i);var n=i(v,d);return c=g-n[0]*h,l=p+n[1]*h,u()}function u(){return s&&(s.valid=!1,s=null),t}var i,o,a,c,l,s,f=nr(function(n,t){return n=i(n,t),[n[0]*h+c,l-n[1]*h]}),h=150,g=480,p=250,v=0,d=0,m=0,M=0,x=0,b=Lc,_=y,w=null,S=null;return t.stream=function(n){return s&&(s.valid=!1),s=or(b(o,f(_(n)))),s.valid=!0,s},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,Lc):He((w=+n)*Da),u()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Ie(n[0][0],n[0][1],n[1][0],n[1][1]):y,u()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(g=+n[0],p=+n[1],r()):[g,p]},t.center=function(n){return arguments.length?(v=n[0]%360*Da,d=n[1]%360*Da,r()):[v*Pa,d*Pa]},t.rotate=function(n){return arguments.length?(m=n[0]%360*Da,M=n[1]%360*Da,x=n.length>2?n[2]%360*Da:0,r()):[m*Pa,M*Pa,x*Pa]},ta.rebind(t,f,"precision"),function(){return i=n.apply(this,arguments),t.invert=i.invert&&e,r()}}function or(n){return rr(n,function(t,e){n.point(t*Da,e*Da)})}function ar(n,t){return[n,t]}function cr(n,t){return[n>qa?n-La:-qa>n?n+La:n,t]}function lr(n,t,e){return n?t||e?Ae(fr(n),hr(t,e)):fr(n):t||e?hr(t,e):cr}function sr(n){return function(t,e){return t+=n,[t>qa?t-La:-qa>t?t+La:t,e]}}function fr(n){var t=sr(n);return t.invert=sr(-n),t}function hr(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,c=Math.sin(n)*e,l=Math.sin(t),s=l*r+a*u;return[Math.atan2(c*i-s*o,a*r-l*u),tt(s*i+c*o)]}var r=Math.cos(n),u=Math.sin(n),i=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,c=Math.sin(n)*e,l=Math.sin(t),s=l*i-c*o;return[Math.atan2(c*i+l*o,a*r+s*u),tt(s*r-a*u)]},e}function gr(n,t){var e=Math.cos(n),r=Math.sin(n);return function(u,i,o,a){var c=o*t;null!=u?(u=pr(e,u),i=pr(e,i),(o>0?i>u:u>i)&&(u+=o*La)):(u=n+o*La,i=n-.5*c);for(var l,s=u;o>0?s>i:i>s;s-=c)a.point((l=xe([e,-r*Math.cos(s),-r*Math.sin(s)]))[0],l[1])}}function pr(n,t){var e=pe(t);e[0]-=n,Me(e);var r=nt(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Ca)%(2*Math.PI)}function vr(n,t,e){var r=ta.range(n,t-Ca,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function dr(n,t,e){var r=ta.range(n,t-Ca,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function mr(n){return n.source}function yr(n){return n.target}function Mr(n,t,e,r){var u=Math.cos(t),i=Math.sin(t),o=Math.cos(r),a=Math.sin(r),c=u*Math.cos(n),l=u*Math.sin(n),s=o*Math.cos(e),f=o*Math.sin(e),h=2*Math.asin(Math.sqrt(it(r-t)+u*o*it(e-n))),g=1/Math.sin(h),p=h?function(n){var t=Math.sin(n*=h)*g,e=Math.sin(h-n)*g,r=e*c+t*s,u=e*l+t*f,o=e*i+t*a;return[Math.atan2(u,r)*Pa,Math.atan2(o,Math.sqrt(r*r+u*u))*Pa]}:function(){return[n*Pa,t*Pa]};return p.distance=h,p}function xr(){function n(n,u){var i=Math.sin(u*=Da),o=Math.cos(u),a=ga((n*=Da)-t),c=Math.cos(a);Yc+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*i-e*o*c)*a),e*i+r*o*c),t=n,e=i,r=o}var t,e,r;Zc.point=function(u,i){t=u*Da,e=Math.sin(i*=Da),r=Math.cos(i),Zc.point=n},Zc.lineEnd=function(){Zc.point=Zc.lineEnd=b}}function br(n,t){function e(t,e){var r=Math.cos(t),u=Math.cos(e),i=n(r*u);return[i*u*Math.sin(t),i*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),u=t(r),i=Math.sin(u),o=Math.cos(u);return[Math.atan2(n*i,r*o),Math.asin(r&&e*i/r)]},e}function _r(n,t){function e(n,t){o>0?-Ra+Ca>t&&(t=-Ra+Ca):t>Ra-Ca&&(t=Ra-Ca);var e=o/Math.pow(u(t),i);return[e*Math.sin(i*n),o-e*Math.cos(i*n)]}var r=Math.cos(n),u=function(n){return Math.tan(qa/4+n/2)},i=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(u(t)/u(n)),o=r*Math.pow(u(n),i)/i;return i?(e.invert=function(n,t){var e=o-t,r=K(i)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/i,2*Math.atan(Math.pow(o/r,1/i))-Ra]},e):Sr}function wr(n,t){function e(n,t){var e=i-t;return[e*Math.sin(u*n),i-e*Math.cos(u*n)]}var r=Math.cos(n),u=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),i=r/u+n;return ga(u)<Ca?ar:(e.invert=function(n,t){var e=i-t;return[Math.atan2(n,e)/u,i-K(u)*Math.sqrt(n*n+e*e)]},e)}function Sr(n,t){return[n,Math.log(Math.tan(qa/4+t/2))]}function kr(n){var t,e=ur(n),r=e.scale,u=e.translate,i=e.clipExtent;return e.scale=function(){var n=r.apply(e,arguments);return n===e?t?e.clipExtent(null):e:n},e.translate=function(){var n=u.apply(e,arguments);return n===e?t?e.clipExtent(null):e:n},e.clipExtent=function(n){var o=i.apply(e,arguments);if(o===e){if(t=null==n){var a=qa*r(),c=u();i([[c[0]-a,c[1]-a],[c[0]+a,c[1]+a]])}}else t&&(o=null);return o},e.clipExtent(null)}function Er(n,t){return[Math.log(Math.tan(qa/4+t/2)),-n]}function Ar(n){return n[0]}function Nr(n){return n[1]}function Cr(n){for(var t=n.length,e=[0,1],r=2,u=2;t>u;u++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[u])<=0;)--r;e[r++]=u}return e.slice(0,r)}function zr(n,t){return n[0]-t[0]||n[1]-t[1]}function qr(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Lr(n,t,e,r){var u=n[0],i=e[0],o=t[0]-u,a=r[0]-i,c=n[1],l=e[1],s=t[1]-c,f=r[1]-l,h=(a*(c-l)-f*(u-i))/(f*o-a*s);return[u+h*o,c+h*s]}function Tr(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Rr(){tu(this),this.edge=this.site=this.circle=null}function Dr(n){var t=el.pop()||new Rr;return t.site=n,t}function Pr(n){Xr(n),Qc.remove(n),el.push(n),tu(n)}function Ur(n){var t=n.circle,e=t.x,r=t.cy,u={x:e,y:r},i=n.P,o=n.N,a=[n];Pr(n);for(var c=i;c.circle&&ga(e-c.circle.x)<Ca&&ga(r-c.circle.cy)<Ca;)i=c.P,a.unshift(c),Pr(c),c=i;a.unshift(c),Xr(c);for(var l=o;l.circle&&ga(e-l.circle.x)<Ca&&ga(r-l.circle.cy)<Ca;)o=l.N,a.push(l),Pr(l),l=o;a.push(l),Xr(l);var s,f=a.length;for(s=1;f>s;++s)l=a[s],c=a[s-1],Kr(l.edge,c.site,l.site,u);c=a[0],l=a[f-1],l.edge=Jr(c.site,l.site,null,u),Vr(c),Vr(l)}function jr(n){for(var t,e,r,u,i=n.x,o=n.y,a=Qc._;a;)if(r=Fr(a,o)-i,r>Ca)a=a.L;else{if(u=i-Hr(a,o),!(u>Ca)){r>-Ca?(t=a.P,e=a):u>-Ca?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var c=Dr(n);if(Qc.insert(t,c),t||e){if(t===e)return Xr(t),e=Dr(t.site),Qc.insert(c,e),c.edge=e.edge=Jr(t.site,c.site),Vr(t),void Vr(e);if(!e)return void(c.edge=Jr(t.site,c.site));Xr(t),Xr(e);var l=t.site,s=l.x,f=l.y,h=n.x-s,g=n.y-f,p=e.site,v=p.x-s,d=p.y-f,m=2*(h*d-g*v),y=h*h+g*g,M=v*v+d*d,x={x:(d*y-g*M)/m+s,y:(h*M-v*y)/m+f};Kr(e.edge,l,p,x),c.edge=Jr(l,n,null,x),e.edge=Jr(n,p,null,x),Vr(t),Vr(e)}}function Fr(n,t){var e=n.site,r=e.x,u=e.y,i=u-t;if(!i)return r;var o=n.P;if(!o)return-1/0;e=o.site;var a=e.x,c=e.y,l=c-t;if(!l)return a;var s=a-r,f=1/i-1/l,h=s/l;return f?(-h+Math.sqrt(h*h-2*f*(s*s/(-2*l)-c+l/2+u-i/2)))/f+r:(r+a)/2}function Hr(n,t){var e=n.N;if(e)return Fr(e,t);var r=n.site;return r.y===t?r.x:1/0}function Or(n){this.site=n,this.edges=[]}function Ir(n){for(var t,e,r,u,i,o,a,c,l,s,f=n[0][0],h=n[1][0],g=n[0][1],p=n[1][1],v=Kc,d=v.length;d--;)if(i=v[d],i&&i.prepare())for(a=i.edges,c=a.length,o=0;c>o;)s=a[o].end(),r=s.x,u=s.y,l=a[++o%c].start(),t=l.x,e=l.y,(ga(r-t)>Ca||ga(u-e)>Ca)&&(a.splice(o,0,new Qr(Gr(i.site,s,ga(r-f)<Ca&&p-u>Ca?{x:f,y:ga(t-f)<Ca?e:p}:ga(u-p)<Ca&&h-r>Ca?{x:ga(e-p)<Ca?t:h,y:p}:ga(r-h)<Ca&&u-g>Ca?{x:h,y:ga(t-h)<Ca?e:g}:ga(u-g)<Ca&&r-f>Ca?{x:ga(e-g)<Ca?t:f,y:g}:null),i.site,null)),++c)}function Yr(n,t){return t.angle-n.angle}function Zr(){tu(this),this.x=this.y=this.arc=this.site=this.cy=null}function Vr(n){var t=n.P,e=n.N;if(t&&e){var r=t.site,u=n.site,i=e.site;if(r!==i){var o=u.x,a=u.y,c=r.x-o,l=r.y-a,s=i.x-o,f=i.y-a,h=2*(c*f-l*s);if(!(h>=-za)){var g=c*c+l*l,p=s*s+f*f,v=(f*g-l*p)/h,d=(c*p-s*g)/h,f=d+a,m=rl.pop()||new Zr;m.arc=n,m.site=u,m.x=v+o,m.y=f+Math.sqrt(v*v+d*d),m.cy=f,n.circle=m;for(var y=null,M=tl._;M;)if(m.y<M.y||m.y===M.y&&m.x<=M.x){if(!M.L){y=M.P;break}M=M.L}else{if(!M.R){y=M;break}M=M.R}tl.insert(y,m),y||(nl=m)}}}}function Xr(n){var t=n.circle;t&&(t.P||(nl=t.N),tl.remove(t),rl.push(t),tu(t),n.circle=null)}function $r(n){for(var t,e=Gc,r=Oe(n[0][0],n[0][1],n[1][0],n[1][1]),u=e.length;u--;)t=e[u],(!Br(t,n)||!r(t)||ga(t.a.x-t.b.x)<Ca&&ga(t.a.y-t.b.y)<Ca)&&(t.a=t.b=null,e.splice(u,1))}function Br(n,t){var e=n.b;if(e)return!0;var r,u,i=n.a,o=t[0][0],a=t[1][0],c=t[0][1],l=t[1][1],s=n.l,f=n.r,h=s.x,g=s.y,p=f.x,v=f.y,d=(h+p)/2,m=(g+v)/2;if(v===g){if(o>d||d>=a)return;if(h>p){if(i){if(i.y>=l)return}else i={x:d,y:c};e={x:d,y:l}}else{if(i){if(i.y<c)return}else i={x:d,y:l};e={x:d,y:c}}}else if(r=(h-p)/(v-g),u=m-r*d,-1>r||r>1)if(h>p){if(i){if(i.y>=l)return}else i={x:(c-u)/r,y:c};e={x:(l-u)/r,y:l}}else{if(i){if(i.y<c)return}else i={x:(l-u)/r,y:l};e={x:(c-u)/r,y:c}}else if(v>g){if(i){if(i.x>=a)return}else i={x:o,y:r*o+u};e={x:a,y:r*a+u}}else{if(i){if(i.x<o)return}else i={x:a,y:r*a+u};e={x:o,y:r*o+u}}return n.a=i,n.b=e,!0}function Wr(n,t){this.l=n,this.r=t,this.a=this.b=null}function Jr(n,t,e,r){var u=new Wr(n,t);return Gc.push(u),e&&Kr(u,n,t,e),r&&Kr(u,t,n,r),Kc[n.i].edges.push(new Qr(u,n,t)),Kc[t.i].edges.push(new Qr(u,t,n)),u}function Gr(n,t,e){var r=new Wr(n,null);return r.a=t,r.b=e,Gc.push(r),r}function Kr(n,t,e,r){n.a||n.b?n.l===e?n.b=r:n.a=r:(n.a=r,n.l=t,n.r=e)}function Qr(n,t,e){var r=n.a,u=n.b;this.edge=n,this.site=t,this.angle=e?Math.atan2(e.y-t.y,e.x-t.x):n.l===t?Math.atan2(u.x-r.x,r.y-u.y):Math.atan2(r.x-u.x,u.y-r.y)}function nu(){this._=null}function tu(n){n.U=n.C=n.L=n.R=n.P=n.N=null}function eu(n,t){var e=t,r=t.R,u=e.U;u?u.L===e?u.L=r:u.R=r:n._=r,r.U=u,e.U=r,e.R=r.L,e.R&&(e.R.U=e),r.L=e}function ru(n,t){var e=t,r=t.L,u=e.U;u?u.L===e?u.L=r:u.R=r:n._=r,r.U=u,e.U=r,e.L=r.R,e.L&&(e.L.U=e),r.R=e}function uu(n){for(;n.L;)n=n.L;return n}function iu(n,t){var e,r,u,i=n.sort(ou).pop();for(Gc=[],Kc=new Array(n.length),Qc=new nu,tl=new nu;;)if(u=nl,i&&(!u||i.y<u.y||i.y===u.y&&i.x<u.x))(i.x!==e||i.y!==r)&&(Kc[i.i]=new Or(i),jr(i),e=i.x,r=i.y),i=n.pop();else{if(!u)break;Ur(u.arc)}t&&($r(t),Ir(t));var o={cells:Kc,edges:Gc};return Qc=tl=Gc=Kc=null,o}function ou(n,t){return t.y-n.y||t.x-n.x}function au(n,t,e){return(n.x-e.x)*(t.y-n.y)-(n.x-t.x)*(e.y-n.y)}function cu(n){return n.x}function lu(n){return n.y}function su(){return{leaf:!0,nodes:[],point:null,x:null,y:null}}function fu(n,t,e,r,u,i){if(!n(t,e,r,u,i)){var o=.5*(e+u),a=.5*(r+i),c=t.nodes;c[0]&&fu(n,c[0],e,r,o,a),c[1]&&fu(n,c[1],o,r,u,a),c[2]&&fu(n,c[2],e,a,o,i),c[3]&&fu(n,c[3],o,a,u,i)}}function hu(n,t,e,r,u,i,o){var a,c=1/0;return function l(n,s,f,h,g){if(!(s>i||f>o||r>h||u>g)){if(p=n.point){var p,v=t-n.x,d=e-n.y,m=v*v+d*d;if(c>m){var y=Math.sqrt(c=m);r=t-y,u=e-y,i=t+y,o=e+y,a=p}}for(var M=n.nodes,x=.5*(s+h),b=.5*(f+g),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:l(n,s,f,x,b);break;case 1:l(n,x,f,h,b);break;case 2:l(n,s,b,x,g);break;case 3:l(n,x,b,h,g)}}}(n,r,u,i,o),a}function gu(n,t){n=ta.rgb(n),t=ta.rgb(t);var e=n.r,r=n.g,u=n.b,i=t.r-e,o=t.g-r,a=t.b-u;return function(n){return"#"+xt(Math.round(e+i*n))+xt(Math.round(r+o*n))+xt(Math.round(u+a*n))}}function pu(n,t){var e,r={},u={};for(e in n)e in t?r[e]=mu(n[e],t[e]):u[e]=n[e];for(e in t)e in n||(u[e]=t[e]);return function(n){for(e in r)u[e]=r[e](n);return u}}function vu(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function du(n,t){var e,r,u,i=il.lastIndex=ol.lastIndex=0,o=-1,a=[],c=[];for(n+="",t+="";(e=il.exec(n))&&(r=ol.exec(t));)(u=r.index)>i&&(u=t.slice(i,u),a[o]?a[o]+=u:a[++o]=u),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,c.push({i:o,x:vu(e,r)})),i=ol.lastIndex;return i<t.length&&(u=t.slice(i),a[o]?a[o]+=u:a[++o]=u),a.length<2?c[0]?(t=c[0].x,function(n){return t(n)+""}):function(){return t}:(t=c.length,function(n){for(var e,r=0;t>r;++r)a[(e=c[r]).i]=e.x(n);return a.join("")})}function mu(n,t){for(var e,r=ta.interpolators.length;--r>=0&&!(e=ta.interpolators[r](n,t)););return e}function yu(n,t){var e,r=[],u=[],i=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e;++e)r.push(mu(n[e],t[e]));for(;i>e;++e)u[e]=n[e];for(;o>e;++e)u[e]=t[e];return function(n){for(e=0;a>e;++e)u[e]=r[e](n);return u}}function Mu(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function xu(n){return function(t){return 1-n(1-t)}}function bu(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function _u(n){return n*n}function wu(n){return n*n*n}function Su(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function ku(n){return function(t){return Math.pow(t,n)}}function Eu(n){return 1-Math.cos(n*Ra)}function Au(n){return Math.pow(2,10*(n-1))}function Nu(n){return 1-Math.sqrt(1-n*n)}function Cu(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/La*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*La/t)}}function zu(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function qu(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Lu(n,t){n=ta.hcl(n),t=ta.hcl(t);var e=n.h,r=n.c,u=n.l,i=t.h-e,o=t.c-r,a=t.l-u;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return st(e+i*n,r+o*n,u+a*n)+""}}function Tu(n,t){n=ta.hsl(n),t=ta.hsl(t);var e=n.h,r=n.s,u=n.l,i=t.h-e,o=t.s-r,a=t.l-u;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return ct(e+i*n,r+o*n,u+a*n)+""}}function Ru(n,t){n=ta.lab(n),t=ta.lab(t);var e=n.l,r=n.a,u=n.b,i=t.l-e,o=t.a-r,a=t.b-u;return function(n){return ht(e+i*n,r+o*n,u+a*n)+""}}function Du(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function Pu(n){var t=[n.a,n.b],e=[n.c,n.d],r=ju(t),u=Uu(t,e),i=ju(Fu(e,t,-u))||0;t[0]*e[1]<e[0]*t[1]&&(t[0]*=-1,t[1]*=-1,r*=-1,u*=-1),this.rotate=(r?Math.atan2(t[1],t[0]):Math.atan2(-e[0],e[1]))*Pa,this.translate=[n.e,n.f],this.scale=[r,i],this.skew=i?Math.atan2(u,i)*Pa:0}function Uu(n,t){return n[0]*t[0]+n[1]*t[1]}function ju(n){var t=Math.sqrt(Uu(n,n));return t&&(n[0]/=t,n[1]/=t),t}function Fu(n,t,e){return n[0]+=e*t[0],n[1]+=e*t[1],n}function Hu(n,t){var e,r=[],u=[],i=ta.transform(n),o=ta.transform(t),a=i.translate,c=o.translate,l=i.rotate,s=o.rotate,f=i.skew,h=o.skew,g=i.scale,p=o.scale;return a[0]!=c[0]||a[1]!=c[1]?(r.push("translate(",null,",",null,")"),u.push({i:1,x:vu(a[0],c[0])},{i:3,x:vu(a[1],c[1])})):r.push(c[0]||c[1]?"translate("+c+")":""),l!=s?(l-s>180?s+=360:s-l>180&&(l+=360),u.push({i:r.push(r.pop()+"rotate(",null,")")-2,x:vu(l,s)})):s&&r.push(r.pop()+"rotate("+s+")"),f!=h?u.push({i:r.push(r.pop()+"skewX(",null,")")-2,x:vu(f,h)}):h&&r.push(r.pop()+"skewX("+h+")"),g[0]!=p[0]||g[1]!=p[1]?(e=r.push(r.pop()+"scale(",null,",",null,")"),u.push({i:e-4,x:vu(g[0],p[0])},{i:e-2,x:vu(g[1],p[1])})):(1!=p[0]||1!=p[1])&&r.push(r.pop()+"scale("+p+")"),e=u.length,function(n){for(var t,i=-1;++i<e;)r[(t=u[i]).i]=t.x(n);return r.join("")}}function Ou(n,t){return t=(t-=n=+n)||1/t,function(e){return(e-n)/t}}function Iu(n,t){return t=(t-=n=+n)||1/t,function(e){return Math.max(0,Math.min(1,(e-n)/t))}}function Yu(n){for(var t=n.source,e=n.target,r=Vu(t,e),u=[t];t!==r;)t=t.parent,u.push(t);for(var i=u.length;e!==r;)u.splice(i,0,e),e=e.parent;return u}function Zu(n){for(var t=[],e=n.parent;null!=e;)t.push(n),n=e,e=e.parent;return t.push(n),t}function Vu(n,t){if(n===t)return n;for(var e=Zu(n),r=Zu(t),u=e.pop(),i=r.pop(),o=null;u===i;)o=u,u=e.pop(),i=r.pop();return o}function Xu(n){n.fixed|=2}function $u(n){n.fixed&=-7}function Bu(n){n.fixed|=4,n.px=n.x,n.py=n.y}function Wu(n){n.fixed&=-5}function Ju(n,t,e){var r=0,u=0;if(n.charge=0,!n.leaf)for(var i,o=n.nodes,a=o.length,c=-1;++c<a;)i=o[c],null!=i&&(Ju(i,t,e),n.charge+=i.charge,r+=i.charge*i.cx,u+=i.charge*i.cy);if(n.point){n.leaf||(n.point.x+=Math.random()-.5,n.point.y+=Math.random()-.5);var l=t*e[n.point.index];n.charge+=n.pointCharge=l,r+=l*n.point.x,u+=l*n.point.y}n.cx=r/n.charge,n.cy=u/n.charge}function Gu(n,t){return ta.rebind(n,t,"sort","children","value"),n.nodes=n,n.links=ri,n}function Ku(n,t){for(var e=[n];null!=(n=e.pop());)if(t(n),(u=n.children)&&(r=u.length))for(var r,u;--r>=0;)e.push(u[r])}function Qu(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(i=n.children)&&(u=i.length))for(var u,i,o=-1;++o<u;)e.push(i[o]);for(;null!=(n=r.pop());)t(n)}function ni(n){return n.children}function ti(n){return n.value}function ei(n,t){return t.value-n.value}function ri(n){return ta.merge(n.map(function(n){return(n.children||[]).map(function(t){return{source:n,target:t}})}))}function ui(n){return n.x}function ii(n){return n.y}function oi(n,t,e){n.y0=t,n.y=e}function ai(n){return ta.range(n.length)}function ci(n){for(var t=-1,e=n[0].length,r=[];++t<e;)r[t]=0;return r}function li(n){for(var t,e=1,r=0,u=n[0][1],i=n.length;i>e;++e)(t=n[e][1])>u&&(r=e,u=t);return r}function si(n){return n.reduce(fi,0)}function fi(n,t){return n+t[1]}function hi(n,t){return gi(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function gi(n,t){for(var e=-1,r=+n[0],u=(n[1]-r)/t,i=[];++e<=t;)i[e]=u*e+r;return i}function pi(n){return[ta.min(n),ta.max(n)]}function vi(n,t){return n.value-t.value}function di(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function mi(n,t){n._pack_next=t,t._pack_prev=n}function yi(n,t){var e=t.x-n.x,r=t.y-n.y,u=n.r+t.r;return.999*u*u>e*e+r*r}function Mi(n){function t(n){s=Math.min(n.x-n.r,s),f=Math.max(n.x+n.r,f),h=Math.min(n.y-n.r,h),g=Math.max(n.y+n.r,g)}if((e=n.children)&&(l=e.length)){var e,r,u,i,o,a,c,l,s=1/0,f=-1/0,h=1/0,g=-1/0;if(e.forEach(xi),r=e[0],r.x=-r.r,r.y=0,t(r),l>1&&(u=e[1],u.x=u.r,u.y=0,t(u),l>2))for(i=e[2],wi(r,u,i),t(i),di(r,i),r._pack_prev=i,di(i,u),u=r._pack_next,o=3;l>o;o++){wi(r,u,i=e[o]);var p=0,v=1,d=1;for(a=u._pack_next;a!==u;a=a._pack_next,v++)if(yi(a,i)){p=1;break}if(1==p)for(c=r._pack_prev;c!==a._pack_prev&&!yi(c,i);c=c._pack_prev,d++);p?(d>v||v==d&&u.r<r.r?mi(r,u=a):mi(r=c,u),o--):(di(r,i),u=i,t(i))}var m=(s+f)/2,y=(h+g)/2,M=0;for(o=0;l>o;o++)i=e[o],i.x-=m,i.y-=y,M=Math.max(M,i.r+Math.sqrt(i.x*i.x+i.y*i.y));n.r=M,e.forEach(bi)}}function xi(n){n._pack_next=n._pack_prev=n}function bi(n){delete n._pack_next,delete n._pack_prev}function _i(n,t,e,r){var u=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,u)for(var i=-1,o=u.length;++i<o;)_i(u[i],t,e,r)}function wi(n,t,e){var r=n.r+e.r,u=t.x-n.x,i=t.y-n.y;if(r&&(u||i)){var o=t.r+e.r,a=u*u+i*i;o*=o,r*=r;var c=.5+(r-o)/(2*a),l=Math.sqrt(Math.max(0,2*o*(r+a)-(r-=a)*r-o*o))/(2*a);e.x=n.x+c*u+l*i,e.y=n.y+c*i-l*u}else e.x=n.x+r,e.y=n.y}function Si(n,t){return n.parent==t.parent?1:2}function ki(n){var t=n.children;return t.length?t[0]:n.t}function Ei(n){var t,e=n.children;return(t=e.length)?e[t-1]:n.t}function Ai(n,t,e){var r=e/(t.i-n.i);t.c-=r,t.s+=e,n.c+=r,t.z+=e,t.m+=e}function Ni(n){for(var t,e=0,r=0,u=n.children,i=u.length;--i>=0;)t=u[i],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Ci(n,t,e){return n.a.parent===t.parent?n.a:e}function zi(n){return 1+ta.max(n,function(n){return n.y})}function qi(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Li(n){var t=n.children;return t&&t.length?Li(t[0]):n}function Ti(n){var t,e=n.children;return e&&(t=e.length)?Ti(e[t-1]):n}function Ri(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Di(n,t){var e=n.x+t[3],r=n.y+t[0],u=n.dx-t[1]-t[3],i=n.dy-t[0]-t[2];return 0>u&&(e+=u/2,u=0),0>i&&(r+=i/2,i=0),{x:e,y:r,dx:u,dy:i}}function Pi(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Ui(n){return n.rangeExtent?n.rangeExtent():Pi(n.range())}function ji(n,t,e,r){var u=e(n[0],n[1]),i=r(t[0],t[1]);return function(n){return i(u(n))}}function Fi(n,t){var e,r=0,u=n.length-1,i=n[r],o=n[u];return i>o&&(e=r,r=u,u=e,e=i,i=o,o=e),n[r]=t.floor(i),n[u]=t.ceil(o),n}function Hi(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:ml}function Oi(n,t,e,r){var u=[],i=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]<n[0]&&(n=n.slice().reverse(),t=t.slice().reverse());++o<=a;)u.push(e(n[o-1],n[o])),i.push(r(t[o-1],t[o]));return function(t){var e=ta.bisect(n,t,1,a)-1;return i[e](u[e](t))}}function Ii(n,t,e,r){function u(){var u=Math.min(n.length,t.length)>2?Oi:ji,c=r?Iu:Ou;return o=u(n,t,c,e),a=u(t,n,c,mu),i}function i(n){return o(n)}var o,a;return i.invert=function(n){return a(n)},i.domain=function(t){return arguments.length?(n=t.map(Number),u()):n},i.range=function(n){return arguments.length?(t=n,u()):t},i.rangeRound=function(n){return i.range(n).interpolate(Du)},i.clamp=function(n){return arguments.length?(r=n,u()):r},i.interpolate=function(n){return arguments.length?(e=n,u()):e},i.ticks=function(t){return Xi(n,t)},i.tickFormat=function(t,e){return $i(n,t,e)},i.nice=function(t){return Zi(n,t),u()},i.copy=function(){return Ii(n,t,e,r)},u()}function Yi(n,t){return ta.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Zi(n,t){return Fi(n,Hi(Vi(n,t)[2]))}function Vi(n,t){null==t&&(t=10);var e=Pi(n),r=e[1]-e[0],u=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),i=t/r*u;return.15>=i?u*=10:.35>=i?u*=5:.75>=i&&(u*=2),e[0]=Math.ceil(e[0]/u)*u,e[1]=Math.floor(e[1]/u)*u+.5*u,e[2]=u,e}function Xi(n,t){return ta.range.apply(ta,Vi(n,t))}function $i(n,t,e){var r=Vi(n,t);if(e){var u=ic.exec(e);if(u.shift(),"s"===u[8]){var i=ta.formatPrefix(Math.max(ga(r[0]),ga(r[1])));return u[7]||(u[7]="."+Bi(i.scale(r[2]))),u[8]="f",e=ta.format(u.join("")),function(n){return e(i.scale(n))+i.symbol}}u[7]||(u[7]="."+Wi(u[8],r)),e=u.join("")}else e=",."+Bi(r[2])+"f";return ta.format(e)}function Bi(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function Wi(n,t){var e=Bi(t[2]);return n in yl?Math.abs(e-Bi(Math.max(ga(t[0]),ga(t[1]))))+ +("e"!==n):e-2*("%"===n)}function Ji(n,t,e,r){function u(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function i(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(u(t))}return o.invert=function(t){return i(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(u)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(u)),o):t},o.nice=function(){var t=Fi(r.map(u),e?Math:xl);return n.domain(t),r=t.map(i),o},o.ticks=function(){var n=Pi(r),o=[],a=n[0],c=n[1],l=Math.floor(u(a)),s=Math.ceil(u(c)),f=t%1?2:t;if(isFinite(s-l)){if(e){for(;s>l;l++)for(var h=1;f>h;h++)o.push(i(l)*h);o.push(i(l))}else for(o.push(i(l));l++<s;)for(var h=f-1;h>0;h--)o.push(i(l)*h);for(l=0;o[l]<a;l++);for(s=o.length;o[s-1]>c;s--);o=o.slice(l,s)}return o},o.tickFormat=function(n,t){if(!arguments.length)return Ml;arguments.length<2?t=Ml:"function"!=typeof t&&(t=ta.format(t));var r,a=Math.max(.1,n/o.ticks().length),c=e?(r=1e-12,Math.ceil):(r=-1e-12,Math.floor);return function(n){return n/i(c(u(n)+r))<=a?t(n):""}},o.copy=function(){return Ji(n.copy(),t,e,r)},Yi(o,n)}function Gi(n,t,e){function r(t){return n(u(t))}var u=Ki(t),i=Ki(1/t);return r.invert=function(t){return i(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(u)),r):e},r.ticks=function(n){return Xi(e,n)},r.tickFormat=function(n,t){return $i(e,n,t)},r.nice=function(n){return r.domain(Zi(e,n))},r.exponent=function(o){return arguments.length?(u=Ki(t=o),i=Ki(1/t),n.domain(e.map(u)),r):t},r.copy=function(){return Gi(n.copy(),t,e)},Yi(r,n)}function Ki(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function Qi(n,t){function e(e){return i[((u.get(e)||("range"===t.t?u.set(e,n.push(e)):0/0))-1)%i.length]}function r(t,e){return ta.range(n.length).map(function(n){return t+e*n})}var u,i,o;return e.domain=function(r){if(!arguments.length)return n;n=[],u=new l;for(var i,o=-1,a=r.length;++o<a;)u.has(i=r[o])||u.set(i,n.push(i));return e[t.t].apply(e,t.a)},e.range=function(n){return arguments.length?(i=n,o=0,t={t:"range",a:arguments},e):i},e.rangePoints=function(u,a){arguments.length<2&&(a=0);var c=u[0],l=u[1],s=n.length<2?(c=(c+l)/2,0):(l-c)/(n.length-1+a);return i=r(c+s*a/2,s),o=0,t={t:"rangePoints",a:arguments},e},e.rangeRoundPoints=function(u,a){arguments.length<2&&(a=0);var c=u[0],l=u[1],s=n.length<2?(c=l=Math.round((c+l)/2),0):(l-c)/(n.length-1+a)|0;return i=r(c+Math.round(s*a/2+(l-c-(n.length-1+a)*s)/2),s),o=0,t={t:"rangeRoundPoints",a:arguments},e},e.rangeBands=function(u,a,c){arguments.length<2&&(a=0),arguments.length<3&&(c=a);var l=u[1]<u[0],s=u[l-0],f=u[1-l],h=(f-s)/(n.length-a+2*c);return i=r(s+h*c,h),l&&i.reverse(),o=h*(1-a),t={t:"rangeBands",a:arguments},e},e.rangeRoundBands=function(u,a,c){arguments.length<2&&(a=0),arguments.length<3&&(c=a);var l=u[1]<u[0],s=u[l-0],f=u[1-l],h=Math.floor((f-s)/(n.length-a+2*c));return i=r(s+Math.round((f-s-(n.length-a)*h)/2),h),l&&i.reverse(),o=Math.round(h*(1-a)),t={t:"rangeRoundBands",a:arguments},e},e.rangeBand=function(){return o},e.rangeExtent=function(){return Pi(t.a[0])},e.copy=function(){return Qi(n,t)},e.domain(n)}function no(n,t){function i(){var e=0,r=t.length;for(a=[];++e<r;)a[e-1]=ta.quantile(n,e/r);return o}function o(n){return isNaN(n=+n)?void 0:t[ta.bisect(a,n)]}var a;return o.domain=function(t){return arguments.length?(n=t.map(r).filter(u).sort(e),i()):n},o.range=function(n){return arguments.length?(t=n,i()):t},o.quantiles=function(){return a},o.invertExtent=function(e){return e=t.indexOf(e),0>e?[0/0,0/0]:[e>0?a[e-1]:n[0],e<a.length?a[e]:n[n.length-1]]},o.copy=function(){return no(n,t)},i()}function to(n,t,e){function r(t){return e[Math.max(0,Math.min(o,Math.floor(i*(t-n))))]}function u(){return i=e.length/(t-n),o=e.length-1,r}var i,o;return r.domain=function(e){return arguments.length?(n=+e[0],t=+e[e.length-1],u()):[n,t]},r.range=function(n){return arguments.length?(e=n,u()):e},r.invertExtent=function(t){return t=e.indexOf(t),t=0>t?0/0:t/i+n,[t,t+1/i]},r.copy=function(){return to(n,t,e)},u()}function eo(n,t){function e(e){return e>=e?t[ta.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return eo(n,t)},e}function ro(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Xi(n,t)},t.tickFormat=function(t,e){return $i(n,t,e)},t.copy=function(){return ro(n)},t}function uo(){return 0}function io(n){return n.innerRadius}function oo(n){return n.outerRadius}function ao(n){return n.startAngle}function co(n){return n.endAngle}function lo(n){return n&&n.padAngle}function so(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function fo(n,t,e,r,u){var i=n[0]-t[0],o=n[1]-t[1],a=(u?r:-r)/Math.sqrt(i*i+o*o),c=a*o,l=-a*i,s=n[0]+c,f=n[1]+l,h=t[0]+c,g=t[1]+l,p=(s+h)/2,v=(f+g)/2,d=h-s,m=g-f,y=d*d+m*m,M=e-r,x=s*g-h*f,b=(0>m?-1:1)*Math.sqrt(M*M*y-x*x),_=(x*m-d*b)/y,w=(-x*d-m*b)/y,S=(x*m+d*b)/y,k=(-x*d+m*b)/y,E=_-p,A=w-v,N=S-p,C=k-v;return E*E+A*A>N*N+C*C&&(_=S,w=k),[[_-c,w-l],[_*e/M,w*e/M]]}function ho(n){function t(t){function o(){l.push("M",i(n(s),a))}for(var c,l=[],s=[],f=-1,h=t.length,g=Et(e),p=Et(r);++f<h;)u.call(this,c=t[f],f)?s.push([+g.call(this,c,f),+p.call(this,c,f)]):s.length&&(o(),s=[]);return s.length&&o(),l.length?l.join(""):null}var e=Ar,r=Nr,u=Ne,i=go,o=i.key,a=.7;return t.x=function(n){return arguments.length?(e=n,t):e},t.y=function(n){return arguments.length?(r=n,t):r},t.defined=function(n){return arguments.length?(u=n,t):u},t.interpolate=function(n){return arguments.length?(o="function"==typeof n?i=n:(i=El.get(n)||go).key,t):o},t.tension=function(n){return arguments.length?(a=n,t):a},t}function go(n){return n.join("L")}function po(n){return go(n)+"Z"}function vo(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t<e;)u.push("H",(r[0]+(r=n[t])[0])/2,"V",r[1]);return e>1&&u.push("H",r[0]),u.join("")}function mo(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t<e;)u.push("V",(r=n[t])[1],"H",r[0]);return u.join("")}function yo(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t<e;)u.push("H",(r=n[t])[0],"V",r[1]);return u.join("")}function Mo(n,t){return n.length<4?go(n):n[1]+_o(n.slice(1,-1),wo(n,t))}function xo(n,t){return n.length<3?go(n):n[0]+_o((n.push(n[0]),n),wo([n[n.length-2]].concat(n,[n[1]]),t))}function bo(n,t){return n.length<3?go(n):n[0]+_o(n,wo(n,t))}function _o(n,t){if(t.length<1||n.length!=t.length&&n.length!=t.length+2)return go(n);var e=n.length!=t.length,r="",u=n[0],i=n[1],o=t[0],a=o,c=1;if(e&&(r+="Q"+(i[0]-2*o[0]/3)+","+(i[1]-2*o[1]/3)+","+i[0]+","+i[1],u=n[1],c=2),t.length>1){a=t[1],i=n[c],c++,r+="C"+(u[0]+o[0])+","+(u[1]+o[1])+","+(i[0]-a[0])+","+(i[1]-a[1])+","+i[0]+","+i[1];for(var l=2;l<t.length;l++,c++)i=n[c],a=t[l],r+="S"+(i[0]-a[0])+","+(i[1]-a[1])+","+i[0]+","+i[1]}if(e){var s=n[c];r+="Q"+(i[0]+2*a[0]/3)+","+(i[1]+2*a[1]/3)+","+s[0]+","+s[1]}return r}function wo(n,t){for(var e,r=[],u=(1-t)/2,i=n[0],o=n[1],a=1,c=n.length;++a<c;)e=i,i=o,o=n[a],r.push([u*(o[0]-e[0]),u*(o[1]-e[1])]);return r}function So(n){if(n.length<3)return go(n);var t=1,e=n.length,r=n[0],u=r[0],i=r[1],o=[u,u,u,(r=n[1])[0]],a=[i,i,i,r[1]],c=[u,",",i,"L",No(Cl,o),",",No(Cl,a)];for(n.push(n[e-1]);++t<=e;)r=n[t],o.shift(),o.push(r[0]),a.shift(),a.push(r[1]),Co(c,o,a);return n.pop(),c.push("L",r),c.join("")}function ko(n){if(n.length<4)return go(n);for(var t,e=[],r=-1,u=n.length,i=[0],o=[0];++r<3;)t=n[r],i.push(t[0]),o.push(t[1]);for(e.push(No(Cl,i)+","+No(Cl,o)),--r;++r<u;)t=n[r],i.shift(),i.push(t[0]),o.shift(),o.push(t[1]),Co(e,i,o);return e.join("")}function Eo(n){for(var t,e,r=-1,u=n.length,i=u+4,o=[],a=[];++r<4;)e=n[r%u],o.push(e[0]),a.push(e[1]);for(t=[No(Cl,o),",",No(Cl,a)],--r;++r<i;)e=n[r%u],o.shift(),o.push(e[0]),a.shift(),a.push(e[1]),Co(t,o,a);return t.join("")}function Ao(n,t){var e=n.length-1;if(e)for(var r,u,i=n[0][0],o=n[0][1],a=n[e][0]-i,c=n[e][1]-o,l=-1;++l<=e;)r=n[l],u=l/e,r[0]=t*r[0]+(1-t)*(i+u*a),r[1]=t*r[1]+(1-t)*(o+u*c);return So(n)}function No(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]+n[3]*t[3]}function Co(n,t,e){n.push("C",No(Al,t),",",No(Al,e),",",No(Nl,t),",",No(Nl,e),",",No(Cl,t),",",No(Cl,e))}function zo(n,t){return(t[1]-n[1])/(t[0]-n[0])}function qo(n){for(var t=0,e=n.length-1,r=[],u=n[0],i=n[1],o=r[0]=zo(u,i);++t<e;)r[t]=(o+(o=zo(u=i,i=n[t+1])))/2;return r[t]=o,r}function Lo(n){for(var t,e,r,u,i=[],o=qo(n),a=-1,c=n.length-1;++a<c;)t=zo(n[a],n[a+1]),ga(t)<Ca?o[a]=o[a+1]=0:(e=o[a]/t,r=o[a+1]/t,u=e*e+r*r,u>9&&(u=3*t/Math.sqrt(u),o[a]=u*e,o[a+1]=u*r));for(a=-1;++a<=c;)u=(n[Math.min(c,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),i.push([u||0,o[a]*u||0]);return i}function To(n){return n.length<3?go(n):n[0]+_o(n,Lo(n))}function Ro(n){for(var t,e,r,u=-1,i=n.length;++u<i;)t=n[u],e=t[0],r=t[1]-Ra,t[0]=e*Math.cos(r),t[1]=e*Math.sin(r);return n}function Do(n){function t(t){function c(){v.push("M",a(n(m),f),s,l(n(d.reverse()),f),"Z")}for(var h,g,p,v=[],d=[],m=[],y=-1,M=t.length,x=Et(e),b=Et(u),_=e===r?function(){return g}:Et(r),w=u===i?function(){return p}:Et(i);++y<M;)o.call(this,h=t[y],y)?(d.push([g=+x.call(this,h,y),p=+b.call(this,h,y)]),m.push([+_.call(this,h,y),+w.call(this,h,y)])):d.length&&(c(),d=[],m=[]);return d.length&&c(),v.length?v.join(""):null}var e=Ar,r=Ar,u=0,i=Nr,o=Ne,a=go,c=a.key,l=a,s="L",f=.7;return t.x=function(n){return arguments.length?(e=r=n,t):r},t.x0=function(n){return arguments.length?(e=n,t):e},t.x1=function(n){return arguments.length?(r=n,t):r
+},t.y=function(n){return arguments.length?(u=i=n,t):i},t.y0=function(n){return arguments.length?(u=n,t):u},t.y1=function(n){return arguments.length?(i=n,t):i},t.defined=function(n){return arguments.length?(o=n,t):o},t.interpolate=function(n){return arguments.length?(c="function"==typeof n?a=n:(a=El.get(n)||go).key,l=a.reverse||a,s=a.closed?"M":"L",t):c},t.tension=function(n){return arguments.length?(f=n,t):f},t}function Po(n){return n.radius}function Uo(n){return[n.x,n.y]}function jo(n){return function(){var t=n.apply(this,arguments),e=t[0],r=t[1]-Ra;return[e*Math.cos(r),e*Math.sin(r)]}}function Fo(){return 64}function Ho(){return"circle"}function Oo(n){var t=Math.sqrt(n/qa);return"M0,"+t+"A"+t+","+t+" 0 1,1 0,"+-t+"A"+t+","+t+" 0 1,1 0,"+t+"Z"}function Io(n){return function(){var t,e;(t=this[n])&&(e=t[t.active])&&(--t.count?delete t[t.active]:delete this[n],t.active+=.5,e.event&&e.event.interrupt.call(this,this.__data__,e.index))}}function Yo(n,t,e){return ya(n,Pl),n.namespace=t,n.id=e,n}function Zo(n,t,e,r){var u=n.id,i=n.namespace;return Y(n,"function"==typeof e?function(n,o,a){n[i][u].tween.set(t,r(e.call(n,n.__data__,o,a)))}:(e=r(e),function(n){n[i][u].tween.set(t,e)}))}function Vo(n){return null==n&&(n=""),function(){this.textContent=n}}function Xo(n){return null==n?"__transition__":"__transition_"+n+"__"}function $o(n,t,e,r,u){var i=n[e]||(n[e]={active:0,count:0}),o=i[r];if(!o){var a=u.time;o=i[r]={tween:new l,time:a,delay:u.delay,duration:u.duration,ease:u.ease,index:t},u=null,++i.count,ta.timer(function(u){function c(e){if(i.active>r)return s();var u=i[i.active];u&&(--i.count,delete i[i.active],u.event&&u.event.interrupt.call(n,n.__data__,u.index)),i.active=r,o.event&&o.event.start.call(n,n.__data__,t),o.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&v.push(r)}),h=o.ease,f=o.duration,ta.timer(function(){return p.c=l(e||1)?Ne:l,1},0,a)}function l(e){if(i.active!==r)return 1;for(var u=e/f,a=h(u),c=v.length;c>0;)v[--c].call(n,a);return u>=1?(o.event&&o.event.end.call(n,n.__data__,t),s()):void 0}function s(){return--i.count?delete i[r]:delete n[e],1}var f,h,g=o.delay,p=ec,v=[];return p.t=g+a,u>=g?c(u-g):void(p.c=c)},0,a)}}function Bo(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function Wo(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function Jo(n){return n.toISOString()}function Go(n,t,e){function r(t){return n(t)}function u(n,e){var r=n[1]-n[0],u=r/e,i=ta.bisect(Vl,u);return i==Vl.length?[t.year,Vi(n.map(function(n){return n/31536e6}),e)[2]]:i?t[u/Vl[i-1]<Vl[i]/u?i-1:i]:[Bl,Vi(n,e)[2]]}return r.invert=function(t){return Ko(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain(t),r):n.domain().map(Ko)},r.nice=function(n,t){function e(e){return!isNaN(e)&&!n.range(e,Ko(+e+1),t).length}var i=r.domain(),o=Pi(i),a=null==n?u(o,10):"number"==typeof n&&u(o,n);return a&&(n=a[0],t=a[1]),r.domain(Fi(i,t>1?{floor:function(t){for(;e(t=n.floor(t));)t=Ko(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=Ko(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Pi(r.domain()),i=null==n?u(e,10):"number"==typeof n?u(e,n):!n.range&&[{range:n},t];return i&&(n=i[0],t=i[1]),n.range(e[0],Ko(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return Go(n.copy(),t,e)},Yi(r,n)}function Ko(n){return new Date(n)}function Qo(n){return JSON.parse(n.responseText)}function na(n){var t=ua.createRange();return t.selectNode(ua.body),t.createContextualFragment(n.responseText)}var ta={version:"3.5.6"},ea=[].slice,ra=function(n){return ea.call(n)},ua=this.document;if(ua)try{ra(ua.documentElement.childNodes)[0].nodeType}catch(ia){ra=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),ua)try{ua.createElement("DIV").style.setProperty("opacity",0,"")}catch(oa){var aa=this.Element.prototype,ca=aa.setAttribute,la=aa.setAttributeNS,sa=this.CSSStyleDeclaration.prototype,fa=sa.setProperty;aa.setAttribute=function(n,t){ca.call(this,n,t+"")},aa.setAttributeNS=function(n,t,e){la.call(this,n,t,e+"")},sa.setProperty=function(n,t,e){fa.call(this,n,t+"",e)}}ta.ascending=e,ta.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:0/0},ta.min=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u<i;)if(null!=(r=n[u])&&r>=r){e=r;break}for(;++u<i;)null!=(r=n[u])&&e>r&&(e=r)}else{for(;++u<i;)if(null!=(r=t.call(n,n[u],u))&&r>=r){e=r;break}for(;++u<i;)null!=(r=t.call(n,n[u],u))&&e>r&&(e=r)}return e},ta.max=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u<i;)if(null!=(r=n[u])&&r>=r){e=r;break}for(;++u<i;)null!=(r=n[u])&&r>e&&(e=r)}else{for(;++u<i;)if(null!=(r=t.call(n,n[u],u))&&r>=r){e=r;break}for(;++u<i;)null!=(r=t.call(n,n[u],u))&&r>e&&(e=r)}return e},ta.extent=function(n,t){var e,r,u,i=-1,o=n.length;if(1===arguments.length){for(;++i<o;)if(null!=(r=n[i])&&r>=r){e=u=r;break}for(;++i<o;)null!=(r=n[i])&&(e>r&&(e=r),r>u&&(u=r))}else{for(;++i<o;)if(null!=(r=t.call(n,n[i],i))&&r>=r){e=u=r;break}for(;++i<o;)null!=(r=t.call(n,n[i],i))&&(e>r&&(e=r),r>u&&(u=r))}return[e,u]},ta.sum=function(n,t){var e,r=0,i=n.length,o=-1;if(1===arguments.length)for(;++o<i;)u(e=+n[o])&&(r+=e);else for(;++o<i;)u(e=+t.call(n,n[o],o))&&(r+=e);return r},ta.mean=function(n,t){var e,i=0,o=n.length,a=-1,c=o;if(1===arguments.length)for(;++a<o;)u(e=r(n[a]))?i+=e:--c;else for(;++a<o;)u(e=r(t.call(n,n[a],a)))?i+=e:--c;return c?i/c:void 0},ta.quantile=function(n,t){var e=(n.length-1)*t+1,r=Math.floor(e),u=+n[r-1],i=e-r;return i?u+i*(n[r]-u):u},ta.median=function(n,t){var i,o=[],a=n.length,c=-1;if(1===arguments.length)for(;++c<a;)u(i=r(n[c]))&&o.push(i);else for(;++c<a;)u(i=r(t.call(n,n[c],c)))&&o.push(i);return o.length?ta.quantile(o.sort(e),.5):void 0},ta.variance=function(n,t){var e,i,o=n.length,a=0,c=0,l=-1,s=0;if(1===arguments.length)for(;++l<o;)u(e=r(n[l]))&&(i=e-a,a+=i/++s,c+=i*(e-a));else for(;++l<o;)u(e=r(t.call(n,n[l],l)))&&(i=e-a,a+=i/++s,c+=i*(e-a));return s>1?c/(s-1):void 0},ta.deviation=function(){var n=ta.variance.apply(this,arguments);return n?Math.sqrt(n):n};var ha=i(e);ta.bisectLeft=ha.left,ta.bisect=ta.bisectRight=ha.right,ta.bisector=function(n){return i(1===n.length?function(t,r){return e(n(t),r)}:n)},ta.shuffle=function(n,t,e){(i=arguments.length)<3&&(e=n.length,2>i&&(t=0));for(var r,u,i=e-t;i;)u=Math.random()*i--|0,r=n[i+t],n[i+t]=n[u+t],n[u+t]=r;return n},ta.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},ta.pairs=function(n){for(var t,e=0,r=n.length-1,u=n[0],i=new Array(0>r?0:r);r>e;)i[e]=[t=u,u=n[++e]];return i},ta.zip=function(){if(!(r=arguments.length))return[];for(var n=-1,t=ta.min(arguments,o),e=new Array(t);++n<t;)for(var r,u=-1,i=e[n]=new Array(r);++u<r;)i[u]=arguments[u][n];return e},ta.transpose=function(n){return ta.zip.apply(ta,n)},ta.keys=function(n){var t=[];for(var e in n)t.push(e);return t},ta.values=function(n){var t=[];for(var e in n)t.push(n[e]);return t},ta.entries=function(n){var t=[];for(var e in n)t.push({key:e,value:n[e]});return t},ta.merge=function(n){for(var t,e,r,u=n.length,i=-1,o=0;++i<u;)o+=n[i].length;for(e=new Array(o);--u>=0;)for(r=n[u],t=r.length;--t>=0;)e[--o]=r[t];return e};var ga=Math.abs;ta.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,u=[],i=a(ga(e)),o=-1;if(n*=i,t*=i,e*=i,0>e)for(;(r=n+e*++o)>t;)u.push(r/i);else for(;(r=n+e*++o)<t;)u.push(r/i);return u},ta.map=function(n,t){var e=new l;if(n instanceof l)n.forEach(function(n,t){e.set(n,t)});else if(Array.isArray(n)){var r,u=-1,i=n.length;if(1===arguments.length)for(;++u<i;)e.set(u,n[u]);else for(;++u<i;)e.set(t.call(n,r=n[u],u),r)}else for(var o in n)e.set(o,n[o]);return e};var pa="__proto__",va="\x00";c(l,{has:h,get:function(n){return this._[s(n)]},set:function(n,t){return this._[s(n)]=t},remove:g,keys:p,values:function(){var n=[];for(var t in this._)n.push(this._[t]);return n},entries:function(){var n=[];for(var t in this._)n.push({key:f(t),value:this._[t]});return n},size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,f(t),this._[t])}}),ta.nest=function(){function n(t,o,a){if(a>=i.length)return r?r.call(u,o):e?o.sort(e):o;for(var c,s,f,h,g=-1,p=o.length,v=i[a++],d=new l;++g<p;)(h=d.get(c=v(s=o[g])))?h.push(s):d.set(c,[s]);return t?(s=t(),f=function(e,r){s.set(e,n(t,r,a))}):(s={},f=function(e,r){s[e]=n(t,r,a)}),d.forEach(f),s}function t(n,e){if(e>=i.length)return n;var r=[],u=o[e++];return n.forEach(function(n,u){r.push({key:n,values:t(u,e)})}),u?r.sort(function(n,t){return u(n.key,t.key)}):r}var e,r,u={},i=[],o=[];return u.map=function(t,e){return n(e,t,0)},u.entries=function(e){return t(n(ta.map,e,0),0)},u.key=function(n){return i.push(n),u},u.sortKeys=function(n){return o[i.length-1]=n,u},u.sortValues=function(n){return e=n,u},u.rollup=function(n){return r=n,u},u},ta.set=function(n){var t=new m;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},c(m,{has:h,add:function(n){return this._[s(n+="")]=!0,n},remove:g,values:p,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,f(t))}}),ta.behavior={},ta.rebind=function(n,t){for(var e,r=1,u=arguments.length;++r<u;)n[e=arguments[r]]=M(n,t,t[e]);return n};var da=["webkit","ms","moz","Moz","o","O"];ta.dispatch=function(){for(var n=new _,t=-1,e=arguments.length;++t<e;)n[arguments[t]]=w(n);return n},_.prototype.on=function(n,t){var e=n.indexOf("."),r="";if(e>=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},ta.event=null,ta.requote=function(n){return n.replace(ma,"\\$&")};var ma=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,ya={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},Ma=function(n,t){return t.querySelector(n)},xa=function(n,t){return t.querySelectorAll(n)},ba=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(ba=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(Ma=function(n,t){return Sizzle(n,t)[0]||null},xa=Sizzle,ba=Sizzle.matchesSelector),ta.selection=function(){return ta.select(ua.documentElement)};var _a=ta.selection.prototype=[];_a.select=function(n){var t,e,r,u,i=[];n=N(n);for(var o=-1,a=this.length;++o<a;){i.push(t=[]),t.parentNode=(r=this[o]).parentNode;for(var c=-1,l=r.length;++c<l;)(u=r[c])?(t.push(e=n.call(u,u.__data__,c,o)),e&&"__data__"in u&&(e.__data__=u.__data__)):t.push(null)}return A(i)},_a.selectAll=function(n){var t,e,r=[];n=C(n);for(var u=-1,i=this.length;++u<i;)for(var o=this[u],a=-1,c=o.length;++a<c;)(e=o[a])&&(r.push(t=ra(n.call(e,e.__data__,a,u))),t.parentNode=e);return A(r)};var wa={svg:"http://www.w3.org/2000/svg",xhtml:"http://www.w3.org/1999/xhtml",xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};ta.ns={prefix:wa,qualify:function(n){var t=n.indexOf(":"),e=n;return t>=0&&(e=n.slice(0,t),n=n.slice(t+1)),wa.hasOwnProperty(e)?{space:wa[e],local:n}:n}},_a.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=ta.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},_a.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,u=-1;if(t=e.classList){for(;++u<r;)if(!t.contains(n[u]))return!1}else for(t=e.getAttribute("class");++u<r;)if(!L(n[u]).test(t))return!1;return!0}for(t in n)this.each(R(t,n[t]));return this}return this.each(R(n,t))},_a.style=function(n,e,r){var u=arguments.length;if(3>u){if("string"!=typeof n){2>u&&(e="");for(r in n)this.each(P(r,n[r],e));return this}if(2>u){var i=this.node();return t(i).getComputedStyle(i,null).getPropertyValue(n)}r=""}return this.each(P(n,e,r))},_a.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(U(t,n[t]));return this}return this.each(U(n,t))},_a.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},_a.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},_a.append=function(n){return n=j(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},_a.insert=function(n,t){return n=j(n),t=N(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},_a.remove=function(){return this.each(F)},_a.data=function(n,t){function e(n,e){var r,u,i,o=n.length,f=e.length,h=Math.min(o,f),g=new Array(f),p=new Array(f),v=new Array(o);if(t){var d,m=new l,y=new Array(o);for(r=-1;++r<o;)m.has(d=t.call(u=n[r],u.__data__,r))?v[r]=u:m.set(d,u),y[r]=d;for(r=-1;++r<f;)(u=m.get(d=t.call(e,i=e[r],r)))?u!==!0&&(g[r]=u,u.__data__=i):p[r]=H(i),m.set(d,!0);for(r=-1;++r<o;)m.get(y[r])!==!0&&(v[r]=n[r])}else{for(r=-1;++r<h;)u=n[r],i=e[r],u?(u.__data__=i,g[r]=u):p[r]=H(i);for(;f>r;++r)p[r]=H(e[r]);for(;o>r;++r)v[r]=n[r]}p.update=g,p.parentNode=g.parentNode=v.parentNode=n.parentNode,a.push(p),c.push(g),s.push(v)}var r,u,i=-1,o=this.length;if(!arguments.length){for(n=new Array(o=(r=this[0]).length);++i<o;)(u=r[i])&&(n[i]=u.__data__);return n}var a=Z([]),c=A([]),s=A([]);if("function"==typeof n)for(;++i<o;)e(r=this[i],n.call(r,r.parentNode.__data__,i));else for(;++i<o;)e(r=this[i],n);return c.enter=function(){return a},c.exit=function(){return s},c},_a.datum=function(n){return arguments.length?this.property("__data__",n):this.property("__data__")},_a.filter=function(n){var t,e,r,u=[];"function"!=typeof n&&(n=O(n));for(var i=0,o=this.length;o>i;i++){u.push(t=[]),t.parentNode=(e=this[i]).parentNode;for(var a=0,c=e.length;c>a;a++)(r=e[a])&&n.call(r,r.__data__,a,i)&&t.push(r)}return A(u)},_a.order=function(){for(var n=-1,t=this.length;++n<t;)for(var e,r=this[n],u=r.length-1,i=r[u];--u>=0;)(e=r[u])&&(i&&i!==e.nextSibling&&i.parentNode.insertBefore(e,i),i=e);return this},_a.sort=function(n){n=I.apply(this,arguments);for(var t=-1,e=this.length;++t<e;)this[t].sort(n);return this.order()},_a.each=function(n){return Y(this,function(t,e,r){n.call(t,t.__data__,e,r)})},_a.call=function(n){var t=ra(arguments);return n.apply(t[0]=this,t),this},_a.empty=function(){return!this.node()},_a.node=function(){for(var n=0,t=this.length;t>n;n++)for(var e=this[n],r=0,u=e.length;u>r;r++){var i=e[r];if(i)return i}return null},_a.size=function(){var n=0;return Y(this,function(){++n}),n};var Sa=[];ta.selection.enter=Z,ta.selection.enter.prototype=Sa,Sa.append=_a.append,Sa.empty=_a.empty,Sa.node=_a.node,Sa.call=_a.call,Sa.size=_a.size,Sa.select=function(n){for(var t,e,r,u,i,o=[],a=-1,c=this.length;++a<c;){r=(u=this[a]).update,o.push(t=[]),t.parentNode=u.parentNode;for(var l=-1,s=u.length;++l<s;)(i=u[l])?(t.push(r[l]=e=n.call(u.parentNode,i.__data__,l,a)),e.__data__=i.__data__):t.push(null)}return A(o)},Sa.insert=function(n,t){return arguments.length<2&&(t=V(this)),_a.insert.call(this,n,t)},ta.select=function(t){var e;return"string"==typeof t?(e=[Ma(t,ua)],e.parentNode=ua.documentElement):(e=[t],e.parentNode=n(t)),A([e])},ta.selectAll=function(n){var t;return"string"==typeof n?(t=ra(xa(n,ua)),t.parentNode=ua.documentElement):(t=n,t.parentNode=null),A([t])},_a.on=function(n,t,e){var r=arguments.length;if(3>r){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var ka=ta.map({mouseenter:"mouseover",mouseleave:"mouseout"});ua&&ka.forEach(function(n){"on"+n in ua&&ka.remove(n)});var Ea,Aa=0;ta.mouse=function(n){return J(n,k())};var Na=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;ta.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,u=0,i=t.length;i>u;++u)if((r=t[u]).identifier===e)return J(n,r)},ta.behavior.drag=function(){function n(){this.on("mousedown.drag",i).on("touchstart.drag",o)}function e(n,t,e,i,o){return function(){function a(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],p|=n|e,M=r,g({type:"drag",x:r[0]+l[0],y:r[1]+l[1],dx:n,dy:e}))}function c(){t(h,v)&&(m.on(i+d,null).on(o+d,null),y(p&&ta.event.target===f),g({type:"dragend"}))}var l,s=this,f=ta.event.target,h=s.parentNode,g=r.of(s,arguments),p=0,v=n(),d=".drag"+(null==v?"":"-"+v),m=ta.select(e(f)).on(i+d,a).on(o+d,c),y=W(f),M=t(h,v);u?(l=u.apply(s,arguments),l=[l.x-M[0],l.y-M[1]]):l=[0,0],g({type:"dragstart"})}}var r=E(n,"drag","dragstart","dragend"),u=null,i=e(b,ta.mouse,t,"mousemove","mouseup"),o=e(G,ta.touch,y,"touchmove","touchend");return n.origin=function(t){return arguments.length?(u=t,n):u},ta.rebind(n,r,"on")},ta.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?ra(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Ca=1e-6,za=Ca*Ca,qa=Math.PI,La=2*qa,Ta=La-Ca,Ra=qa/2,Da=qa/180,Pa=180/qa,Ua=Math.SQRT2,ja=2,Fa=4;ta.interpolateZoom=function(n,t){function e(n){var t=n*y;if(m){var e=rt(v),o=i/(ja*h)*(e*ut(Ua*t+v)-et(v));return[r+o*l,u+o*s,i*e/rt(Ua*t+v)]}return[r+n*l,u+n*s,i*Math.exp(Ua*t)]}var r=n[0],u=n[1],i=n[2],o=t[0],a=t[1],c=t[2],l=o-r,s=a-u,f=l*l+s*s,h=Math.sqrt(f),g=(c*c-i*i+Fa*f)/(2*i*ja*h),p=(c*c-i*i-Fa*f)/(2*c*ja*h),v=Math.log(Math.sqrt(g*g+1)-g),d=Math.log(Math.sqrt(p*p+1)-p),m=d-v,y=(m||Math.log(c/i))/Ua;return e.duration=1e3*y,e},ta.behavior.zoom=function(){function n(n){n.on(q,f).on(Oa+".zoom",g).on("dblclick.zoom",p).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function u(n){k.k=Math.max(N[0],Math.min(N[1],n))}function i(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function o(t,e,r,o){t.__chart__={x:k.x,y:k.y,k:k.k},u(Math.pow(2,o)),i(d=e,r),t=ta.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function a(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function c(n){z++||n({type:"zoomstart"})}function l(n){a(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function s(n){--z||(n({type:"zoomend"}),d=null)}function f(){function n(){f=1,i(ta.mouse(u),g),l(a)}function r(){h.on(L,null).on(T,null),p(f&&ta.event.target===o),s(a)}var u=this,o=ta.event.target,a=D.of(u,arguments),f=0,h=ta.select(t(u)).on(L,n).on(T,r),g=e(ta.mouse(u)),p=W(u);Dl.call(u),c(a)}function h(){function n(){var n=ta.touches(p);return g=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=ta.event.target;ta.select(t).on(x,r).on(b,a),_.push(t);for(var e=ta.event.changedTouches,u=0,i=e.length;i>u;++u)d[e[u].identifier]=null;var c=n(),l=Date.now();if(1===c.length){if(500>l-M){var s=c[0];o(p,s,d[s.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=l}else if(c.length>1){var s=c[0],f=c[1],h=s[0]-f[0],g=s[1]-f[1];m=h*h+g*g}}function r(){var n,t,e,r,o=ta.touches(p);Dl.call(p);for(var a=0,c=o.length;c>a;++a,r=null)if(e=o[a],r=d[e.identifier]){if(t)break;n=e,t=r}if(r){var s=(s=e[0]-n[0])*s+(s=e[1]-n[1])*s,f=m&&Math.sqrt(s/m);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],u(f*g)}M=null,i(n,t),l(v)}function a(){if(ta.event.touches.length){for(var t=ta.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var u in d)return void n()}ta.selectAll(_).on(y,null),w.on(q,f).on(R,h),E(),s(v)}var g,p=this,v=D.of(p,arguments),d={},m=0,y=".zoom-"+ta.event.changedTouches[0].identifier,x="touchmove"+y,b="touchend"+y,_=[],w=ta.select(p),E=W(p);t(),c(v),w.on(q,null).on(R,t)}function g(){var n=D.of(this,arguments);y?clearTimeout(y):(Dl.call(this),v=e(d=m||ta.mouse(this)),c(n)),y=setTimeout(function(){y=null,s(n)},50),S(),u(Math.pow(2,.002*Ha())*k.k),i(d,v),l(n)}function p(){var n=ta.mouse(this),t=Math.log(k.k)/Math.LN2;o(this,n,e(n),ta.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,m,y,M,x,b,_,w,k={x:0,y:0,k:1},A=[960,500],N=Ia,C=250,z=0,q="mousedown.zoom",L="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoom",D=E(n,"zoomstart","zoom","zoomend");return Oa||(Oa="onwheel"in ua?(Ha=function(){return-ta.event.deltaY*(ta.event.deltaMode?120:1)},"wheel"):"onmousewheel"in ua?(Ha=function(){return ta.event.wheelDelta},"mousewheel"):(Ha=function(){return-ta.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Tl?ta.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},c(n)}).tween("zoom:zoom",function(){var e=A[0],r=A[1],u=d?d[0]:e/2,i=d?d[1]:r/2,o=ta.interpolateZoom([(u-k.x)/k.k,(i-k.y)/k.k,e/k.k],[(u-t.x)/t.k,(i-t.y)/t.k,e/t.k]);return function(t){var r=o(t),a=e/r[2];this.__chart__=k={x:u-r[0]*a,y:i-r[1]*a,k:a},l(n)}}).each("interrupt.zoom",function(){s(n)}).each("end.zoom",function(){s(n)}):(this.__chart__=k,c(n),l(n),s(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},a(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:+t},a(),n):k.k},n.scaleExtent=function(t){return arguments.length?(N=null==t?Ia:[+t[0],+t[1]],n):N},n.center=function(t){return arguments.length?(m=t&&[+t[0],+t[1]],n):m},n.size=function(t){return arguments.length?(A=t&&[+t[0],+t[1]],n):A},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},ta.rebind(n,D,"on")};var Ha,Oa,Ia=[0,1/0];ta.color=ot,ot.prototype.toString=function(){return this.rgb()+""},ta.hsl=at;var Ya=at.prototype=new ot;Ya.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new at(this.h,this.s,this.l/n)},Ya.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new at(this.h,this.s,n*this.l)},Ya.rgb=function(){return ct(this.h,this.s,this.l)},ta.hcl=lt;var Za=lt.prototype=new ot;Za.brighter=function(n){return new lt(this.h,this.c,Math.min(100,this.l+Va*(arguments.length?n:1)))},Za.darker=function(n){return new lt(this.h,this.c,Math.max(0,this.l-Va*(arguments.length?n:1)))},Za.rgb=function(){return st(this.h,this.c,this.l).rgb()},ta.lab=ft;var Va=18,Xa=.95047,$a=1,Ba=1.08883,Wa=ft.prototype=new ot;Wa.brighter=function(n){return new ft(Math.min(100,this.l+Va*(arguments.length?n:1)),this.a,this.b)},Wa.darker=function(n){return new ft(Math.max(0,this.l-Va*(arguments.length?n:1)),this.a,this.b)},Wa.rgb=function(){return ht(this.l,this.a,this.b)},ta.rgb=mt;var Ja=mt.prototype=new ot;Ja.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,u=30;return t||e||r?(t&&u>t&&(t=u),e&&u>e&&(e=u),r&&u>r&&(r=u),new mt(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new mt(u,u,u)},Ja.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new mt(n*this.r,n*this.g,n*this.b)},Ja.hsl=function(){return _t(this.r,this.g,this.b)},Ja.toString=function(){return"#"+xt(this.r)+xt(this.g)+xt(this.b)};var Ga=ta.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});Ga.forEach(function(n,t){Ga.set(n,yt(t))}),ta.functor=Et,ta.xhr=At(y),ta.dsv=function(n,t){function e(n,e,i){arguments.length<3&&(i=e,e=null);var o=Nt(n,t,null==e?r:u(e),i);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:u(n)):e},o}function r(n){return e.parse(n.responseText)}function u(n){return function(t){return e.parse(t.responseText,n)}}function i(t){return t.map(o).join(n)}function o(n){return a.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var a=new RegExp('["'+n+"\n]"),c=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var u=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(u(n),e)}:u})},e.parseRows=function(n,t){function e(){if(s>=l)return o;if(u)return u=!1,i;var t=s;if(34===n.charCodeAt(t)){for(var e=t;e++<l;)if(34===n.charCodeAt(e)){if(34!==n.charCodeAt(e+1))break;++e}s=e+2;var r=n.charCodeAt(e+1);return 13===r?(u=!0,10===n.charCodeAt(e+2)&&++s):10===r&&(u=!0),n.slice(t+1,e).replace(/""/g,'"')}for(;l>s;){var r=n.charCodeAt(s++),a=1;if(10===r)u=!0;else if(13===r)u=!0,10===n.charCodeAt(s)&&(++s,++a);else if(r!==c)continue;return n.slice(t,s-a)}return n.slice(t)}for(var r,u,i={},o={},a=[],l=n.length,s=0,f=0;(r=e())!==o;){for(var h=[];r!==i&&r!==o;)h.push(r),r=e();t&&null==(h=t(h,f++))||a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new m,u=[];return t.forEach(function(n){for(var t in n)r.has(t)||u.push(r.add(t))}),[u.map(o).join(n)].concat(t.map(function(t){return u.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(i).join("\n")},e},ta.csv=ta.dsv(",","text/csv"),ta.tsv=ta.dsv(" ","text/tab-separated-values");var Ka,Qa,nc,tc,ec,rc=this[x(this,"requestAnimationFrame")]||function(n){setTimeout(n,17)};ta.timer=function(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var u=e+t,i={c:n,t:u,f:!1,n:null};Qa?Qa.n=i:Ka=i,Qa=i,nc||(tc=clearTimeout(tc),nc=1,rc(qt))},ta.timer.flush=function(){Lt(),Tt()},ta.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var uc=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Dt);ta.formatPrefix=function(n,t){var e=0;return n&&(0>n&&(n*=-1),t&&(n=ta.round(n,Rt(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),uc[8+e/3]};var ic=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,oc=ta.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=ta.round(n,Rt(n,t))).toFixed(Math.max(0,Math.min(20,Rt(n*(1+1e-15),t))))}}),ac=ta.time={},cc=Date;jt.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){lc.setUTCDate.apply(this._,arguments)},setDay:function(){lc.setUTCDay.apply(this._,arguments)},setFullYear:function(){lc.setUTCFullYear.apply(this._,arguments)},setHours:function(){lc.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){lc.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){lc.setUTCMinutes.apply(this._,arguments)},setMonth:function(){lc.setUTCMonth.apply(this._,arguments)},setSeconds:function(){lc.setUTCSeconds.apply(this._,arguments)},setTime:function(){lc.setTime.apply(this._,arguments)}};var lc=Date.prototype;ac.year=Ft(function(n){return n=ac.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),ac.years=ac.year.range,ac.years.utc=ac.year.utc.range,ac.day=Ft(function(n){var t=new cc(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),ac.days=ac.day.range,ac.days.utc=ac.day.utc.range,ac.dayOfYear=function(n){var t=ac.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=ac[n]=Ft(function(n){return(n=ac.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=ac.year(n).getDay();return Math.floor((ac.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});ac[n+"s"]=e.range,ac[n+"s"].utc=e.utc.range,ac[n+"OfYear"]=function(n){var e=ac.year(n).getDay();return Math.floor((ac.dayOfYear(n)+(e+t)%7)/7)}}),ac.week=ac.sunday,ac.weeks=ac.sunday.range,ac.weeks.utc=ac.sunday.utc.range,ac.weekOfYear=ac.sundayOfYear;var sc={"-":"",_:" ",0:"0"},fc=/^\s*\d+/,hc=/^%/;ta.locale=function(n){return{numberFormat:Pt(n),timeFormat:Ot(n)}};var gc=ta.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});ta.format=gc.numberFormat,ta.geo={},ce.prototype={s:0,t:0,add:function(n){le(n,this.t,pc),le(pc.s,this.s,this),this.s?this.t+=pc.t:this.s=pc.t
+},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var pc=new ce;ta.geo.stream=function(n,t){n&&vc.hasOwnProperty(n.type)?vc[n.type](n,t):se(n,t)};var vc={Feature:function(n,t){se(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,u=e.length;++r<u;)se(e[r].geometry,t)}},dc={Sphere:function(n,t){t.sphere()},Point:function(n,t){n=n.coordinates,t.point(n[0],n[1],n[2])},MultiPoint:function(n,t){for(var e=n.coordinates,r=-1,u=e.length;++r<u;)n=e[r],t.point(n[0],n[1],n[2])},LineString:function(n,t){fe(n.coordinates,t,0)},MultiLineString:function(n,t){for(var e=n.coordinates,r=-1,u=e.length;++r<u;)fe(e[r],t,0)},Polygon:function(n,t){he(n.coordinates,t)},MultiPolygon:function(n,t){for(var e=n.coordinates,r=-1,u=e.length;++r<u;)he(e[r],t)},GeometryCollection:function(n,t){for(var e=n.geometries,r=-1,u=e.length;++r<u;)se(e[r],t)}};ta.geo.area=function(n){return mc=0,ta.geo.stream(n,Mc),mc};var mc,yc=new ce,Mc={sphere:function(){mc+=4*qa},point:b,lineStart:b,lineEnd:b,polygonStart:function(){yc.reset(),Mc.lineStart=ge},polygonEnd:function(){var n=2*yc;mc+=0>n?4*qa+n:n,Mc.lineStart=Mc.lineEnd=Mc.point=b}};ta.geo.bounds=function(){function n(n,t){M.push(x=[s=n,h=n]),f>t&&(f=t),t>g&&(g=t)}function t(t,e){var r=pe([t*Da,e*Da]);if(m){var u=de(m,r),i=[u[1],-u[0],0],o=de(i,u);Me(o),o=xe(o);var c=t-p,l=c>0?1:-1,v=o[0]*Pa*l,d=ga(c)>180;if(d^(v>l*p&&l*t>v)){var y=o[1]*Pa;y>g&&(g=y)}else if(v=(v+360)%360-180,d^(v>l*p&&l*t>v)){var y=-o[1]*Pa;f>y&&(f=y)}else f>e&&(f=e),e>g&&(g=e);d?p>t?a(s,t)>a(s,h)&&(h=t):a(t,h)>a(s,h)&&(s=t):h>=s?(s>t&&(s=t),t>h&&(h=t)):t>p?a(s,t)>a(s,h)&&(h=t):a(t,h)>a(s,h)&&(s=t)}else n(t,e);m=r,p=t}function e(){b.point=t}function r(){x[0]=s,x[1]=h,b.point=n,m=null}function u(n,e){if(m){var r=n-p;y+=ga(r)>180?r+(r>0?360:-360):r}else v=n,d=e;Mc.point(n,e),t(n,e)}function i(){Mc.lineStart()}function o(){u(v,d),Mc.lineEnd(),ga(y)>Ca&&(s=-(h=180)),x[0]=s,x[1]=h,m=null}function a(n,t){return(t-=n)<0?t+360:t}function c(n,t){return n[0]-t[0]}function l(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:n<t[0]||t[1]<n}var s,f,h,g,p,v,d,m,y,M,x,b={point:n,lineStart:e,lineEnd:r,polygonStart:function(){b.point=u,b.lineStart=i,b.lineEnd=o,y=0,Mc.polygonStart()},polygonEnd:function(){Mc.polygonEnd(),b.point=n,b.lineStart=e,b.lineEnd=r,0>yc?(s=-(h=180),f=-(g=90)):y>Ca?g=90:-Ca>y&&(f=-90),x[0]=s,x[1]=h}};return function(n){g=h=-(s=f=1/0),M=[],ta.geo.stream(n,b);var t=M.length;if(t){M.sort(c);for(var e,r=1,u=M[0],i=[u];t>r;++r)e=M[r],l(e[0],u)||l(e[1],u)?(a(u[0],e[1])>a(u[0],u[1])&&(u[1]=e[1]),a(e[0],u[1])>a(u[0],u[1])&&(u[0]=e[0])):i.push(u=e);for(var o,e,p=-1/0,t=i.length-1,r=0,u=i[t];t>=r;u=e,++r)e=i[r],(o=a(u[1],e[0]))>p&&(p=o,s=e[0],h=u[1])}return M=x=null,1/0===s||1/0===f?[[0/0,0/0],[0/0,0/0]]:[[s,f],[h,g]]}}(),ta.geo.centroid=function(n){xc=bc=_c=wc=Sc=kc=Ec=Ac=Nc=Cc=zc=0,ta.geo.stream(n,qc);var t=Nc,e=Cc,r=zc,u=t*t+e*e+r*r;return za>u&&(t=kc,e=Ec,r=Ac,Ca>bc&&(t=_c,e=wc,r=Sc),u=t*t+e*e+r*r,za>u)?[0/0,0/0]:[Math.atan2(e,t)*Pa,tt(r/Math.sqrt(u))*Pa]};var xc,bc,_c,wc,Sc,kc,Ec,Ac,Nc,Cc,zc,qc={sphere:b,point:_e,lineStart:Se,lineEnd:ke,polygonStart:function(){qc.lineStart=Ee},polygonEnd:function(){qc.lineStart=Se}},Lc=Le(Ne,Pe,je,[-qa,-qa/2]),Tc=1e9;ta.geo.clipExtent=function(){var n,t,e,r,u,i,o={stream:function(n){return u&&(u.valid=!1),u=i(n),u.valid=!0,u},extent:function(a){return arguments.length?(i=Ie(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),u&&(u.valid=!1,u=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(ta.geo.conicEqualArea=function(){return Ye(Ze)}).raw=Ze,ta.geo.albers=function(){return ta.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},ta.geo.albersUsa=function(){function n(n){var i=n[0],o=n[1];return t=null,e(i,o),t||(r(i,o),t)||u(i,o),t}var t,e,r,u,i=ta.geo.albers(),o=ta.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=ta.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),c={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=i.scale(),e=i.translate(),r=(n[0]-e[0])/t,u=(n[1]-e[1])/t;return(u>=.12&&.234>u&&r>=-.425&&-.214>r?o:u>=.166&&.234>u&&r>=-.214&&-.115>r?a:i).invert(n)},n.stream=function(n){var t=i.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,u){t.point(n,u),e.point(n,u),r.point(n,u)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(i.precision(t),o.precision(t),a.precision(t),n):i.precision()},n.scale=function(t){return arguments.length?(i.scale(t),o.scale(.35*t),a.scale(t),n.translate(i.translate())):i.scale()},n.translate=function(t){if(!arguments.length)return i.translate();var l=i.scale(),s=+t[0],f=+t[1];return e=i.translate(t).clipExtent([[s-.455*l,f-.238*l],[s+.455*l,f+.238*l]]).stream(c).point,r=o.translate([s-.307*l,f+.201*l]).clipExtent([[s-.425*l+Ca,f+.12*l+Ca],[s-.214*l-Ca,f+.234*l-Ca]]).stream(c).point,u=a.translate([s-.205*l,f+.212*l]).clipExtent([[s-.214*l+Ca,f+.166*l+Ca],[s-.115*l-Ca,f+.234*l-Ca]]).stream(c).point,n},n.scale(1070)};var Rc,Dc,Pc,Uc,jc,Fc,Hc={point:b,lineStart:b,lineEnd:b,polygonStart:function(){Dc=0,Hc.lineStart=Ve},polygonEnd:function(){Hc.lineStart=Hc.lineEnd=Hc.point=b,Rc+=ga(Dc/2)}},Oc={point:Xe,lineStart:b,lineEnd:b,polygonStart:b,polygonEnd:b},Ic={point:We,lineStart:Je,lineEnd:Ge,polygonStart:function(){Ic.lineStart=Ke},polygonEnd:function(){Ic.point=We,Ic.lineStart=Je,Ic.lineEnd=Ge}};ta.geo.path=function(){function n(n){return n&&("function"==typeof a&&i.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=u(i)),ta.geo.stream(n,o)),i.result()}function t(){return o=null,n}var e,r,u,i,o,a=4.5;return n.area=function(n){return Rc=0,ta.geo.stream(n,u(Hc)),Rc},n.centroid=function(n){return _c=wc=Sc=kc=Ec=Ac=Nc=Cc=zc=0,ta.geo.stream(n,u(Ic)),zc?[Nc/zc,Cc/zc]:Ac?[kc/Ac,Ec/Ac]:Sc?[_c/Sc,wc/Sc]:[0/0,0/0]},n.bounds=function(n){return jc=Fc=-(Pc=Uc=1/0),ta.geo.stream(n,u(Oc)),[[Pc,Uc],[jc,Fc]]},n.projection=function(n){return arguments.length?(u=(e=n)?n.stream||tr(n):y,t()):e},n.context=function(n){return arguments.length?(i=null==(r=n)?new $e:new Qe(n),"function"!=typeof a&&i.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(i.pointRadius(+t),+t),n):a},n.projection(ta.geo.albersUsa()).context(null)},ta.geo.transform=function(n){return{stream:function(t){var e=new er(t);for(var r in n)e[r]=n[r];return e}}},er.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},ta.geo.projection=ur,ta.geo.projectionMutator=ir,(ta.geo.equirectangular=function(){return ur(ar)}).raw=ar.invert=ar,ta.geo.rotation=function(n){function t(t){return t=n(t[0]*Da,t[1]*Da),t[0]*=Pa,t[1]*=Pa,t}return n=lr(n[0]%360*Da,n[1]*Da,n.length>2?n[2]*Da:0),t.invert=function(t){return t=n.invert(t[0]*Da,t[1]*Da),t[0]*=Pa,t[1]*=Pa,t},t},cr.invert=ar,ta.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=lr(-n[0]*Da,-n[1]*Da,0).invert,u=[];return e(null,null,1,{point:function(n,e){u.push(n=t(n,e)),n[0]*=Pa,n[1]*=Pa}}),{type:"Polygon",coordinates:[u]}}var t,e,r=[0,0],u=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=gr((t=+r)*Da,u*Da),n):t},n.precision=function(r){return arguments.length?(e=gr(t*Da,(u=+r)*Da),n):u},n.angle(90)},ta.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Da,u=n[1]*Da,i=t[1]*Da,o=Math.sin(r),a=Math.cos(r),c=Math.sin(u),l=Math.cos(u),s=Math.sin(i),f=Math.cos(i);return Math.atan2(Math.sqrt((e=f*o)*e+(e=l*s-c*f*a)*e),c*s+l*f*a)},ta.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return ta.range(Math.ceil(i/d)*d,u,d).map(h).concat(ta.range(Math.ceil(l/m)*m,c,m).map(g)).concat(ta.range(Math.ceil(r/p)*p,e,p).filter(function(n){return ga(n%d)>Ca}).map(s)).concat(ta.range(Math.ceil(a/v)*v,o,v).filter(function(n){return ga(n%m)>Ca}).map(f))}var e,r,u,i,o,a,c,l,s,f,h,g,p=10,v=p,d=90,m=360,y=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(i).concat(g(c).slice(1),h(u).reverse().slice(1),g(l).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(i=+t[0][0],u=+t[1][0],l=+t[0][1],c=+t[1][1],i>u&&(t=i,i=u,u=t),l>c&&(t=l,l=c,c=t),n.precision(y)):[[i,l],[u,c]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(y)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],m=+t[1],n):[d,m]},n.minorStep=function(t){return arguments.length?(p=+t[0],v=+t[1],n):[p,v]},n.precision=function(t){return arguments.length?(y=+t,s=vr(a,o,90),f=dr(r,e,y),h=vr(l,c,90),g=dr(i,u,y),n):y},n.majorExtent([[-180,-90+Ca],[180,90-Ca]]).minorExtent([[-180,-80-Ca],[180,80+Ca]])},ta.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||u.apply(this,arguments)]}}var t,e,r=mr,u=yr;return n.distance=function(){return ta.geo.distance(t||r.apply(this,arguments),e||u.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(u=t,e="function"==typeof t?null:t,n):u},n.precision=function(){return arguments.length?n:0},n},ta.geo.interpolate=function(n,t){return Mr(n[0]*Da,n[1]*Da,t[0]*Da,t[1]*Da)},ta.geo.length=function(n){return Yc=0,ta.geo.stream(n,Zc),Yc};var Yc,Zc={sphere:b,point:b,lineStart:xr,lineEnd:b,polygonStart:b,polygonEnd:b},Vc=br(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(ta.geo.azimuthalEqualArea=function(){return ur(Vc)}).raw=Vc;var Xc=br(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},y);(ta.geo.azimuthalEquidistant=function(){return ur(Xc)}).raw=Xc,(ta.geo.conicConformal=function(){return Ye(_r)}).raw=_r,(ta.geo.conicEquidistant=function(){return Ye(wr)}).raw=wr;var $c=br(function(n){return 1/n},Math.atan);(ta.geo.gnomonic=function(){return ur($c)}).raw=$c,Sr.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Ra]},(ta.geo.mercator=function(){return kr(Sr)}).raw=Sr;var Bc=br(function(){return 1},Math.asin);(ta.geo.orthographic=function(){return ur(Bc)}).raw=Bc;var Wc=br(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(ta.geo.stereographic=function(){return ur(Wc)}).raw=Wc,Er.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Ra]},(ta.geo.transverseMercator=function(){var n=kr(Er),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Er,ta.geom={},ta.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,u=Et(e),i=Et(r),o=n.length,a=[],c=[];for(t=0;o>t;t++)a.push([+u.call(this,n[t],t),+i.call(this,n[t],t),t]);for(a.sort(zr),t=0;o>t;t++)c.push([a[t][0],-a[t][1]]);var l=Cr(a),s=Cr(c),f=s[0]===l[0],h=s[s.length-1]===l[l.length-1],g=[];for(t=l.length-1;t>=0;--t)g.push(n[a[l[t]][2]]);for(t=+f;t<s.length-h;++t)g.push(n[a[s[t]][2]]);return g}var e=Ar,r=Nr;return arguments.length?t(n):(t.x=function(n){return arguments.length?(e=n,t):e},t.y=function(n){return arguments.length?(r=n,t):r},t)},ta.geom.polygon=function(n){return ya(n,Jc),n};var Jc=ta.geom.polygon.prototype=[];Jc.area=function(){for(var n,t=-1,e=this.length,r=this[e-1],u=0;++t<e;)n=r,r=this[t],u+=n[1]*r[0]-n[0]*r[1];return.5*u},Jc.centroid=function(n){var t,e,r=-1,u=this.length,i=0,o=0,a=this[u-1];for(arguments.length||(n=-1/(6*this.area()));++r<u;)t=a,a=this[r],e=t[0]*a[1]-a[0]*t[1],i+=(t[0]+a[0])*e,o+=(t[1]+a[1])*e;return[i*n,o*n]},Jc.clip=function(n){for(var t,e,r,u,i,o,a=Tr(n),c=-1,l=this.length-Tr(this),s=this[l-1];++c<l;){for(t=n.slice(),n.length=0,u=this[c],i=t[(r=t.length-a)-1],e=-1;++e<r;)o=t[e],qr(o,s,u)?(qr(i,s,u)||n.push(Lr(i,o,s,u)),n.push(o)):qr(i,s,u)&&n.push(Lr(i,o,s,u)),i=o;a&&n.push(n[0]),s=u}return n};var Gc,Kc,Qc,nl,tl,el=[],rl=[];Or.prototype.prepare=function(){for(var n,t=this.edges,e=t.length;e--;)n=t[e].edge,n.b&&n.a||t.splice(e,1);return t.sort(Yr),t.length},Qr.prototype={start:function(){return this.edge.l===this.site?this.edge.a:this.edge.b},end:function(){return this.edge.l===this.site?this.edge.b:this.edge.a}},nu.prototype={insert:function(n,t){var e,r,u;if(n){if(t.P=n,t.N=n.N,n.N&&(n.N.P=t),n.N=t,n.R){for(n=n.R;n.L;)n=n.L;n.L=t}else n.R=t;e=n}else this._?(n=uu(this._),t.P=null,t.N=n,n.P=n.L=t,e=n):(t.P=t.N=null,this._=t,e=null);for(t.L=t.R=null,t.U=e,t.C=!0,n=t;e&&e.C;)r=e.U,e===r.L?(u=r.R,u&&u.C?(e.C=u.C=!1,r.C=!0,n=r):(n===e.R&&(eu(this,e),n=e,e=n.U),e.C=!1,r.C=!0,ru(this,r))):(u=r.L,u&&u.C?(e.C=u.C=!1,r.C=!0,n=r):(n===e.L&&(ru(this,e),n=e,e=n.U),e.C=!1,r.C=!0,eu(this,r))),e=n.U;this._.C=!1},remove:function(n){n.N&&(n.N.P=n.P),n.P&&(n.P.N=n.N),n.N=n.P=null;var t,e,r,u=n.U,i=n.L,o=n.R;if(e=i?o?uu(o):i:o,u?u.L===n?u.L=e:u.R=e:this._=e,i&&o?(r=e.C,e.C=n.C,e.L=i,i.U=e,e!==o?(u=e.U,e.U=n.U,n=e.R,u.L=n,e.R=o,o.U=e):(e.U=u,u=e,n=e.R)):(r=n.C,n=e),n&&(n.U=u),!r){if(n&&n.C)return void(n.C=!1);do{if(n===this._)break;if(n===u.L){if(t=u.R,t.C&&(t.C=!1,u.C=!0,eu(this,u),t=u.R),t.L&&t.L.C||t.R&&t.R.C){t.R&&t.R.C||(t.L.C=!1,t.C=!0,ru(this,t),t=u.R),t.C=u.C,u.C=t.R.C=!1,eu(this,u),n=this._;break}}else if(t=u.L,t.C&&(t.C=!1,u.C=!0,ru(this,u),t=u.L),t.L&&t.L.C||t.R&&t.R.C){t.L&&t.L.C||(t.R.C=!1,t.C=!0,eu(this,t),t=u.L),t.C=u.C,u.C=t.L.C=!1,ru(this,u),n=this._;break}t.C=!0,n=u,u=u.U}while(!n.C);n&&(n.C=!1)}}},ta.geom.voronoi=function(n){function t(n){var t=new Array(n.length),r=a[0][0],u=a[0][1],i=a[1][0],o=a[1][1];return iu(e(n),a).cells.forEach(function(e,a){var c=e.edges,l=e.site,s=t[a]=c.length?c.map(function(n){var t=n.start();return[t.x,t.y]}):l.x>=r&&l.x<=i&&l.y>=u&&l.y<=o?[[r,o],[i,o],[i,u],[r,u]]:[];s.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(i(n,t)/Ca)*Ca,y:Math.round(o(n,t)/Ca)*Ca,i:t}})}var r=Ar,u=Nr,i=r,o=u,a=ul;return n?t(n):(t.links=function(n){return iu(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return iu(e(n)).cells.forEach(function(e,r){for(var u,i,o=e.site,a=e.edges.sort(Yr),c=-1,l=a.length,s=a[l-1].edge,f=s.l===o?s.r:s.l;++c<l;)u=s,i=f,s=a[c].edge,f=s.l===o?s.r:s.l,r<i.i&&r<f.i&&au(o,i,f)<0&&t.push([n[r],n[i.i],n[f.i]])}),t},t.x=function(n){return arguments.length?(i=Et(r=n),t):r},t.y=function(n){return arguments.length?(o=Et(u=n),t):u},t.clipExtent=function(n){return arguments.length?(a=null==n?ul:n,t):a===ul?null:a},t.size=function(n){return arguments.length?t.clipExtent(n&&[[0,0],n]):a===ul?null:a&&a[1]},t)};var ul=[[-1e6,-1e6],[1e6,1e6]];ta.geom.delaunay=function(n){return ta.geom.voronoi().triangles(n)},ta.geom.quadtree=function(n,t,e,r,u){function i(n){function i(n,t,e,r,u,i,o,a){if(!isNaN(e)&&!isNaN(r))if(n.leaf){var c=n.x,s=n.y;if(null!=c)if(ga(c-e)+ga(s-r)<.01)l(n,t,e,r,u,i,o,a);else{var f=n.point;n.x=n.y=n.point=null,l(n,f,c,s,u,i,o,a),l(n,t,e,r,u,i,o,a)}else n.x=e,n.y=r,n.point=t}else l(n,t,e,r,u,i,o,a)}function l(n,t,e,r,u,o,a,c){var l=.5*(u+a),s=.5*(o+c),f=e>=l,h=r>=s,g=h<<1|f;n.leaf=!1,n=n.nodes[g]||(n.nodes[g]=su()),f?u=l:a=l,h?o=s:c=s,i(n,t,e,r,u,o,a,c)}var s,f,h,g,p,v,d,m,y,M=Et(a),x=Et(c);if(null!=t)v=t,d=e,m=r,y=u;else if(m=y=-(v=d=1/0),f=[],h=[],p=n.length,o)for(g=0;p>g;++g)s=n[g],s.x<v&&(v=s.x),s.y<d&&(d=s.y),s.x>m&&(m=s.x),s.y>y&&(y=s.y),f.push(s.x),h.push(s.y);else for(g=0;p>g;++g){var b=+M(s=n[g],g),_=+x(s,g);v>b&&(v=b),d>_&&(d=_),b>m&&(m=b),_>y&&(y=_),f.push(b),h.push(_)}var w=m-v,S=y-d;w>S?y=d+w:m=v+S;var k=su();if(k.add=function(n){i(k,n,+M(n,++g),+x(n,g),v,d,m,y)},k.visit=function(n){fu(n,k,v,d,m,y)},k.find=function(n){return hu(k,n[0],n[1],v,d,m,y)},g=-1,null==t){for(;++g<p;)i(k,n[g],f[g],h[g],v,d,m,y);--g}else n.forEach(k.add);return f=h=n=s=null,k}var o,a=Ar,c=Nr;return(o=arguments.length)?(a=cu,c=lu,3===o&&(u=e,r=t,e=t=0),i(n)):(i.x=function(n){return arguments.length?(a=n,i):a},i.y=function(n){return arguments.length?(c=n,i):c},i.extent=function(n){return arguments.length?(null==n?t=e=r=u=null:(t=+n[0][0],e=+n[0][1],r=+n[1][0],u=+n[1][1]),i):null==t?null:[[t,e],[r,u]]},i.size=function(n){return arguments.length?(null==n?t=e=r=u=null:(t=e=0,r=+n[0],u=+n[1]),i):null==t?null:[r-t,u-e]},i)},ta.interpolateRgb=gu,ta.interpolateObject=pu,ta.interpolateNumber=vu,ta.interpolateString=du;var il=/[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g,ol=new RegExp(il.source,"g");ta.interpolate=mu,ta.interpolators=[function(n,t){var e=typeof t;return("string"===e?Ga.has(t.toLowerCase())||/^(#|rgb\(|hsl\()/i.test(t)?gu:du:t instanceof ot?gu:Array.isArray(t)?yu:"object"===e&&isNaN(t)?pu:vu)(n,t)}],ta.interpolateArray=yu;var al=function(){return y},cl=ta.map({linear:al,poly:ku,quad:function(){return _u},cubic:function(){return wu},sin:function(){return Eu},exp:function(){return Au},circle:function(){return Nu},elastic:Cu,back:zu,bounce:function(){return qu}}),ll=ta.map({"in":y,out:xu,"in-out":bu,"out-in":function(n){return bu(xu(n))}});ta.ease=function(n){var t=n.indexOf("-"),e=t>=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=cl.get(e)||al,r=ll.get(r)||y,Mu(r(e.apply(null,ea.call(arguments,1))))},ta.interpolateHcl=Lu,ta.interpolateHsl=Tu,ta.interpolateLab=Ru,ta.interpolateRound=Du,ta.transform=function(n){var t=ua.createElementNS(ta.ns.prefix.svg,"g");return(ta.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new Pu(e?e.matrix:sl)})(n)},Pu.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var sl={a:1,b:0,c:0,d:1,e:0,f:0};ta.interpolateTransform=Hu,ta.layout={},ta.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++e<r;)t.push(Yu(n[e]));return t}},ta.layout.chord=function(){function n(){var n,l,f,h,g,p={},v=[],d=ta.range(i),m=[];for(e=[],r=[],n=0,h=-1;++h<i;){for(l=0,g=-1;++g<i;)l+=u[h][g];v.push(l),m.push(ta.range(i)),n+=l}for(o&&d.sort(function(n,t){return o(v[n],v[t])}),a&&m.forEach(function(n,t){n.sort(function(n,e){return a(u[t][n],u[t][e])})}),n=(La-s*i)/n,l=0,h=-1;++h<i;){for(f=l,g=-1;++g<i;){var y=d[h],M=m[y][g],x=u[y][M],b=l,_=l+=x*n;p[y+"-"+M]={index:y,subindex:M,startAngle:b,endAngle:_,value:x}}r[y]={index:y,startAngle:f,endAngle:l,value:(l-f)/n},l+=s}for(h=-1;++h<i;)for(g=h-1;++g<i;){var w=p[h+"-"+g],S=p[g+"-"+h];(w.value||S.value)&&e.push(w.value<S.value?{source:S,target:w}:{source:w,target:S})}c&&t()}function t(){e.sort(function(n,t){return c((n.source.value+n.target.value)/2,(t.source.value+t.target.value)/2)})}var e,r,u,i,o,a,c,l={},s=0;return l.matrix=function(n){return arguments.length?(i=(u=n)&&u.length,e=r=null,l):u},l.padding=function(n){return arguments.length?(s=n,e=r=null,l):s},l.sortGroups=function(n){return arguments.length?(o=n,e=r=null,l):o},l.sortSubgroups=function(n){return arguments.length?(a=n,e=null,l):a},l.sortChords=function(n){return arguments.length?(c=n,e&&t(),l):c},l.chords=function(){return e||n(),e},l.groups=function(){return r||n(),r},l},ta.layout.force=function(){function n(n){return function(t,e,r,u){if(t.point!==n){var i=t.cx-n.x,o=t.cy-n.y,a=u-e,c=i*i+o*o;if(c>a*a/d){if(p>c){var l=t.charge/c;n.px-=i*l,n.py-=o*l}return!0}if(t.point&&c&&p>c){var l=t.pointCharge/c;n.px-=i*l,n.py-=o*l}}return!t.charge}}function t(n){n.px=ta.event.x,n.py=ta.event.y,a.resume()}var e,r,u,i,o,a={},c=ta.dispatch("start","tick","end"),l=[1,1],s=.9,f=fl,h=hl,g=-30,p=gl,v=.1,d=.64,m=[],M=[];return a.tick=function(){if((r*=.99)<.005)return c.end({type:"end",alpha:r=0}),!0;var t,e,a,f,h,p,d,y,x,b=m.length,_=M.length;for(e=0;_>e;++e)a=M[e],f=a.source,h=a.target,y=h.x-f.x,x=h.y-f.y,(p=y*y+x*x)&&(p=r*i[e]*((p=Math.sqrt(p))-u[e])/p,y*=p,x*=p,h.x-=y*(d=f.weight/(h.weight+f.weight)),h.y-=x*d,f.x+=y*(d=1-d),f.y+=x*d);if((d=r*v)&&(y=l[0]/2,x=l[1]/2,e=-1,d))for(;++e<b;)a=m[e],a.x+=(y-a.x)*d,a.y+=(x-a.y)*d;if(g)for(Ju(t=ta.geom.quadtree(m),r,o),e=-1;++e<b;)(a=m[e]).fixed||t.visit(n(a));for(e=-1;++e<b;)a=m[e],a.fixed?(a.x=a.px,a.y=a.py):(a.x-=(a.px-(a.px=a.x))*s,a.y-=(a.py-(a.py=a.y))*s);c.tick({type:"tick",alpha:r})},a.nodes=function(n){return arguments.length?(m=n,a):m},a.links=function(n){return arguments.length?(M=n,a):M},a.size=function(n){return arguments.length?(l=n,a):l},a.linkDistance=function(n){return arguments.length?(f="function"==typeof n?n:+n,a):f},a.distance=a.linkDistance,a.linkStrength=function(n){return arguments.length?(h="function"==typeof n?n:+n,a):h},a.friction=function(n){return arguments.length?(s=+n,a):s},a.charge=function(n){return arguments.length?(g="function"==typeof n?n:+n,a):g},a.chargeDistance=function(n){return arguments.length?(p=n*n,a):Math.sqrt(p)},a.gravity=function(n){return arguments.length?(v=+n,a):v},a.theta=function(n){return arguments.length?(d=n*n,a):Math.sqrt(d)},a.alpha=function(n){return arguments.length?(n=+n,r?r=n>0?n:0:n>0&&(c.start({type:"start",alpha:r=n}),ta.timer(a.tick)),a):r},a.start=function(){function n(n,r){if(!e){for(e=new Array(c),a=0;c>a;++a)e[a]=[];for(a=0;s>a;++a){var u=M[a];e[u.source.index].push(u.target),e[u.target.index].push(u.source)}}for(var i,o=e[t],a=-1,l=o.length;++a<l;)if(!isNaN(i=o[a][n]))return i;return Math.random()*r}var t,e,r,c=m.length,s=M.length,p=l[0],v=l[1];for(t=0;c>t;++t)(r=m[t]).index=t,r.weight=0;for(t=0;s>t;++t)r=M[t],"number"==typeof r.source&&(r.source=m[r.source]),"number"==typeof r.target&&(r.target=m[r.target]),++r.source.weight,++r.target.weight;for(t=0;c>t;++t)r=m[t],isNaN(r.x)&&(r.x=n("x",p)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(u=[],"function"==typeof f)for(t=0;s>t;++t)u[t]=+f.call(this,M[t],t);else for(t=0;s>t;++t)u[t]=f;if(i=[],"function"==typeof h)for(t=0;s>t;++t)i[t]=+h.call(this,M[t],t);else for(t=0;s>t;++t)i[t]=h;if(o=[],"function"==typeof g)for(t=0;c>t;++t)o[t]=+g.call(this,m[t],t);else for(t=0;c>t;++t)o[t]=g;return a.resume()},a.resume=function(){return a.alpha(.1)},a.stop=function(){return a.alpha(0)},a.drag=function(){return e||(e=ta.behavior.drag().origin(y).on("dragstart.force",Xu).on("drag.force",t).on("dragend.force",$u)),arguments.length?void this.on("mouseover.force",Bu).on("mouseout.force",Wu).call(e):e},ta.rebind(a,c,"on")};var fl=20,hl=1,gl=1/0;ta.layout.hierarchy=function(){function n(u){var i,o=[u],a=[];for(u.depth=0;null!=(i=o.pop());)if(a.push(i),(l=e.call(n,i,i.depth))&&(c=l.length)){for(var c,l,s;--c>=0;)o.push(s=l[c]),s.parent=i,s.depth=i.depth+1;r&&(i.value=0),i.children=l}else r&&(i.value=+r.call(n,i,i.depth)||0),delete i.children;return Qu(u,function(n){var e,u;t&&(e=n.children)&&e.sort(t),r&&(u=n.parent)&&(u.value+=n.value)}),a}var t=ei,e=ni,r=ti;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(Ku(t,function(n){n.children&&(n.value=0)}),Qu(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},ta.layout.partition=function(){function n(t,e,r,u){var i=t.children;if(t.x=e,t.y=t.depth*u,t.dx=r,t.dy=u,i&&(o=i.length)){var o,a,c,l=-1;for(r=t.value?r/t.value:0;++l<o;)n(a=i[l],e,c=a.value*r,u),e+=c}}function t(n){var e=n.children,r=0;if(e&&(u=e.length))for(var u,i=-1;++i<u;)r=Math.max(r,t(e[i]));return 1+r}function e(e,i){var o=r.call(this,e,i);return n(o[0],0,u[0],u[1]/t(o[0])),o}var r=ta.layout.hierarchy(),u=[1,1];return e.size=function(n){return arguments.length?(u=n,e):u},Gu(e,r)},ta.layout.pie=function(){function n(o){var a,c=o.length,l=o.map(function(e,r){return+t.call(n,e,r)}),s=+("function"==typeof r?r.apply(this,arguments):r),f=("function"==typeof u?u.apply(this,arguments):u)-s,h=Math.min(Math.abs(f)/c,+("function"==typeof i?i.apply(this,arguments):i)),g=h*(0>f?-1:1),p=(f-c*g)/ta.sum(l),v=ta.range(c),d=[];return null!=e&&v.sort(e===pl?function(n,t){return l[t]-l[n]}:function(n,t){return e(o[n],o[t])}),v.forEach(function(n){d[n]={data:o[n],value:a=l[n],startAngle:s,endAngle:s+=a*p+g,padAngle:h}}),d}var t=Number,e=pl,r=0,u=La,i=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(u=t,n):u},n.padAngle=function(t){return arguments.length?(i=t,n):i},n};var pl={};ta.layout.stack=function(){function n(a,c){if(!(h=a.length))return a;var l=a.map(function(e,r){return t.call(n,e,r)}),s=l.map(function(t){return t.map(function(t,e){return[i.call(n,t,e),o.call(n,t,e)]})}),f=e.call(n,s,c);l=ta.permute(l,f),s=ta.permute(s,f);var h,g,p,v,d=r.call(n,s,c),m=l[0].length;for(p=0;m>p;++p)for(u.call(n,l[0][p],v=d[p],s[0][p][1]),g=1;h>g;++g)u.call(n,l[g][p],v+=s[g-1][p][1],s[g][p][1]);return a}var t=y,e=ai,r=ci,u=oi,i=ui,o=ii;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:vl.get(t)||ai,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:dl.get(t)||ci,n):r},n.x=function(t){return arguments.length?(i=t,n):i},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(u=t,n):u},n};var vl=ta.map({"inside-out":function(n){var t,e,r=n.length,u=n.map(li),i=n.map(si),o=ta.range(r).sort(function(n,t){return u[n]-u[t]}),a=0,c=0,l=[],s=[];for(t=0;r>t;++t)e=o[t],c>a?(a+=i[e],l.push(e)):(c+=i[e],s.push(e));return s.reverse().concat(l)},reverse:function(n){return ta.range(n.length).reverse()},"default":ai}),dl=ta.map({silhouette:function(n){var t,e,r,u=n.length,i=n[0].length,o=[],a=0,c=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;i>e;++e)c[e]=(a-o[e])/2;return c},wiggle:function(n){var t,e,r,u,i,o,a,c,l,s=n.length,f=n[0],h=f.length,g=[];for(g[0]=c=l=0,e=1;h>e;++e){for(t=0,u=0;s>t;++t)u+=n[t][e][1];for(t=0,i=0,a=f[e][0]-f[e-1][0];s>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;i+=o*n[t][e][1]}g[e]=c-=u?i/u*a:0,l>c&&(l=c)}for(e=0;h>e;++e)g[e]-=l;return g},expand:function(n){var t,e,r,u=n.length,i=n[0].length,o=1/u,a=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];if(r)for(t=0;u>t;t++)n[t][e][1]/=r;else for(t=0;u>t;t++)n[t][e][1]=o}for(e=0;i>e;++e)a[e]=0;return a},zero:ci});ta.layout.histogram=function(){function n(n,i){for(var o,a,c=[],l=n.map(e,this),s=r.call(this,l,i),f=u.call(this,s,l,i),i=-1,h=l.length,g=f.length-1,p=t?1:1/h;++i<g;)o=c[i]=[],o.dx=f[i+1]-(o.x=f[i]),o.y=0;if(g>0)for(i=-1;++i<h;)a=l[i],a>=s[0]&&a<=s[1]&&(o=c[ta.bisect(f,a,1,g)-1],o.y+=p,o.push(n[i]));return c}var t=!0,e=Number,r=pi,u=hi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=Et(t),n):r},n.bins=function(t){return arguments.length?(u="number"==typeof t?function(n){return gi(n,t)}:Et(t),n):u},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},ta.layout.pack=function(){function n(n,i){var o=e.call(this,n,i),a=o[0],c=u[0],l=u[1],s=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,Qu(a,function(n){n.r=+s(n.value)}),Qu(a,Mi),r){var f=r*(t?1:Math.max(2*a.r/c,2*a.r/l))/2;Qu(a,function(n){n.r+=f}),Qu(a,Mi),Qu(a,function(n){n.r-=f})}return _i(a,c/2,l/2,t?1:1/Math.max(2*a.r/c,2*a.r/l)),o}var t,e=ta.layout.hierarchy().sort(vi),r=0,u=[1,1];return n.size=function(t){return arguments.length?(u=t,n):u},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},Gu(n,e)},ta.layout.tree=function(){function n(n,u){var s=o.call(this,n,u),f=s[0],h=t(f);if(Qu(h,e),h.parent.m=-h.z,Ku(h,r),l)Ku(f,i);else{var g=f,p=f,v=f;Ku(f,function(n){n.x<g.x&&(g=n),n.x>p.x&&(p=n),n.depth>v.depth&&(v=n)});var d=a(g,p)/2-g.x,m=c[0]/(p.x+a(p,g)/2+d),y=c[1]/(v.depth||1);Ku(f,function(n){n.x=(n.x+d)*m,n.y=n.depth*y})}return s}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var u,i=t.children,o=0,a=i.length;a>o;++o)r.push((i[o]=u={_:i[o],parent:t,children:(u=i[o].children)&&u.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:o}).a=u);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Ni(n);var i=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+a(n._,r._),n.m=n.z-i):n.z=i}else r&&(n.z=r.z+a(n._,r._));n.parent.A=u(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function u(n,t,e){if(t){for(var r,u=n,i=n,o=t,c=u.parent.children[0],l=u.m,s=i.m,f=o.m,h=c.m;o=Ei(o),u=ki(u),o&&u;)c=ki(c),i=Ei(i),i.a=n,r=o.z+f-u.z-l+a(o._,u._),r>0&&(Ai(Ci(o,n,e),n,r),l+=r,s+=r),f+=o.m,l+=u.m,h+=c.m,s+=i.m;o&&!Ei(i)&&(i.t=o,i.m+=f-s),u&&!ki(c)&&(c.t=u,c.m+=l-h,e=n)}return e}function i(n){n.x*=c[0],n.y=n.depth*c[1]}var o=ta.layout.hierarchy().sort(null).value(null),a=Si,c=[1,1],l=null;return n.separation=function(t){return arguments.length?(a=t,n):a},n.size=function(t){return arguments.length?(l=null==(c=t)?i:null,n):l?null:c},n.nodeSize=function(t){return arguments.length?(l=null==(c=t)?null:i,n):l?c:null},Gu(n,o)},ta.layout.cluster=function(){function n(n,i){var o,a=t.call(this,n,i),c=a[0],l=0;Qu(c,function(n){var t=n.children;t&&t.length?(n.x=qi(t),n.y=zi(t)):(n.x=o?l+=e(n,o):0,n.y=0,o=n)});var s=Li(c),f=Ti(c),h=s.x-e(s,f)/2,g=f.x+e(f,s)/2;return Qu(c,u?function(n){n.x=(n.x-c.x)*r[0],n.y=(c.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(g-h)*r[0],n.y=(1-(c.y?n.y/c.y:1))*r[1]}),a}var t=ta.layout.hierarchy().sort(null).value(null),e=Si,r=[1,1],u=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(u=null==(r=t),n):u?null:r},n.nodeSize=function(t){return arguments.length?(u=null!=(r=t),n):u?r:null},Gu(n,t)},ta.layout.treemap=function(){function n(n,t){for(var e,r,u=-1,i=n.length;++u<i;)r=(e=n[u]).value*(0>t?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var i=e.children;if(i&&i.length){var o,a,c,l=f(e),s=[],h=i.slice(),p=1/0,v="slice"===g?l.dx:"dice"===g?l.dy:"slice-dice"===g?1&e.depth?l.dy:l.dx:Math.min(l.dx,l.dy);for(n(h,l.dx*l.dy/e.value),s.area=0;(c=h.length)>0;)s.push(o=h[c-1]),s.area+=o.area,"squarify"!==g||(a=r(s,v))<=p?(h.pop(),p=a):(s.area-=s.pop().area,u(s,v,l,!1),v=Math.min(l.dx,l.dy),s.length=s.area=0,p=1/0);s.length&&(u(s,v,l,!0),s.length=s.area=0),i.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var i,o=f(t),a=r.slice(),c=[];for(n(a,o.dx*o.dy/t.value),c.area=0;i=a.pop();)c.push(i),c.area+=i.area,null!=i.z&&(u(c,i.z?o.dx:o.dy,o,!a.length),c.length=c.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,u=0,i=1/0,o=-1,a=n.length;++o<a;)(e=n[o].area)&&(i>e&&(i=e),e>u&&(u=e));return r*=r,t*=t,r?Math.max(t*u*p/r,r/(t*i*p)):1/0}function u(n,t,e,r){var u,i=-1,o=n.length,a=e.x,l=e.y,s=t?c(n.area/t):0;if(t==e.dx){for((r||s>e.dy)&&(s=e.dy);++i<o;)u=n[i],u.x=a,u.y=l,u.dy=s,a+=u.dx=Math.min(e.x+e.dx-a,s?c(u.area/s):0);u.z=!0,u.dx+=e.x+e.dx-a,e.y+=s,e.dy-=s}else{for((r||s>e.dx)&&(s=e.dx);++i<o;)u=n[i],u.x=a,u.y=l,u.dx=s,l+=u.dy=Math.min(e.y+e.dy-l,s?c(u.area/s):0);u.z=!1,u.dy+=e.y+e.dy-l,e.x+=s,e.dx-=s}}function i(r){var u=o||a(r),i=u[0];return i.x=0,i.y=0,i.dx=l[0],i.dy=l[1],o&&a.revalue(i),n([i],i.dx*i.dy/i.value),(o?e:t)(i),h&&(o=u),u}var o,a=ta.layout.hierarchy(),c=Math.round,l=[1,1],s=null,f=Ri,h=!1,g="squarify",p=.5*(1+Math.sqrt(5));
+return i.size=function(n){return arguments.length?(l=n,i):l},i.padding=function(n){function t(t){var e=n.call(i,t,t.depth);return null==e?Ri(t):Di(t,"number"==typeof e?[e,e,e,e]:e)}function e(t){return Di(t,n)}if(!arguments.length)return s;var r;return f=null==(s=n)?Ri:"function"==(r=typeof n)?t:"number"===r?(n=[n,n,n,n],e):e,i},i.round=function(n){return arguments.length?(c=n?Math.round:Number,i):c!=Number},i.sticky=function(n){return arguments.length?(h=n,o=null,i):h},i.ratio=function(n){return arguments.length?(p=n,i):p},i.mode=function(n){return arguments.length?(g=n+"",i):g},Gu(i,a)},ta.random={normal:function(n,t){var e=arguments.length;return 2>e&&(t=1),1>e&&(n=0),function(){var e,r,u;do e=2*Math.random()-1,r=2*Math.random()-1,u=e*e+r*r;while(!u||u>1);return n+t*e*Math.sqrt(-2*Math.log(u)/u)}},logNormal:function(){var n=ta.random.normal.apply(ta,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=ta.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},ta.scale={};var ml={floor:y,ceil:y};ta.scale.linear=function(){return Ii([0,1],[0,1],mu,!1)};var yl={s:1,g:1,p:1,r:1,e:1};ta.scale.log=function(){return Ji(ta.scale.linear().domain([0,1]),10,!0,[1,10])};var Ml=ta.format(".0e"),xl={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};ta.scale.pow=function(){return Gi(ta.scale.linear(),1,[0,1])},ta.scale.sqrt=function(){return ta.scale.pow().exponent(.5)},ta.scale.ordinal=function(){return Qi([],{t:"range",a:[[]]})},ta.scale.category10=function(){return ta.scale.ordinal().range(bl)},ta.scale.category20=function(){return ta.scale.ordinal().range(_l)},ta.scale.category20b=function(){return ta.scale.ordinal().range(wl)},ta.scale.category20c=function(){return ta.scale.ordinal().range(Sl)};var bl=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(Mt),_l=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(Mt),wl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(Mt),Sl=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(Mt);ta.scale.quantile=function(){return no([],[])},ta.scale.quantize=function(){return to(0,1,[0,1])},ta.scale.threshold=function(){return eo([.5],[0,1])},ta.scale.identity=function(){return ro([0,1])},ta.svg={},ta.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),l=Math.max(0,+r.apply(this,arguments)),s=o.apply(this,arguments)-Ra,f=a.apply(this,arguments)-Ra,h=Math.abs(f-s),g=s>f?0:1;if(n>l&&(p=l,l=n,n=p),h>=Ta)return t(l,g)+(n?t(n,1-g):"")+"Z";var p,v,d,m,y,M,x,b,_,w,S,k,E=0,A=0,N=[];if((m=(+c.apply(this,arguments)||0)/2)&&(d=i===kl?Math.sqrt(n*n+l*l):+i.apply(this,arguments),g||(A*=-1),l&&(A=tt(d/l*Math.sin(m))),n&&(E=tt(d/n*Math.sin(m)))),l){y=l*Math.cos(s+A),M=l*Math.sin(s+A),x=l*Math.cos(f-A),b=l*Math.sin(f-A);var C=Math.abs(f-s-2*A)<=qa?0:1;if(A&&so(y,M,x,b)===g^C){var z=(s+f)/2;y=l*Math.cos(z),M=l*Math.sin(z),x=b=null}}else y=M=0;if(n){_=n*Math.cos(f-E),w=n*Math.sin(f-E),S=n*Math.cos(s+E),k=n*Math.sin(s+E);var q=Math.abs(s-f+2*E)<=qa?0:1;if(E&&so(_,w,S,k)===1-g^q){var L=(s+f)/2;_=n*Math.cos(L),w=n*Math.sin(L),S=k=null}}else _=w=0;if((p=Math.min(Math.abs(l-n)/2,+u.apply(this,arguments)))>.001){v=l>n^g?0:1;var T=null==S?[_,w]:null==x?[y,M]:Lr([y,M],[S,k],[x,b],[_,w]),R=y-T[0],D=M-T[1],P=x-T[0],U=b-T[1],j=1/Math.sin(Math.acos((R*P+D*U)/(Math.sqrt(R*R+D*D)*Math.sqrt(P*P+U*U)))/2),F=Math.sqrt(T[0]*T[0]+T[1]*T[1]);if(null!=x){var H=Math.min(p,(l-F)/(j+1)),O=fo(null==S?[_,w]:[S,k],[y,M],l,H,g),I=fo([x,b],[_,w],l,H,g);p===H?N.push("M",O[0],"A",H,",",H," 0 0,",v," ",O[1],"A",l,",",l," 0 ",1-g^so(O[1][0],O[1][1],I[1][0],I[1][1]),",",g," ",I[1],"A",H,",",H," 0 0,",v," ",I[0]):N.push("M",O[0],"A",H,",",H," 0 1,",v," ",I[0])}else N.push("M",y,",",M);if(null!=S){var Y=Math.min(p,(n-F)/(j-1)),Z=fo([y,M],[S,k],n,-Y,g),V=fo([_,w],null==x?[y,M]:[x,b],n,-Y,g);p===Y?N.push("L",V[0],"A",Y,",",Y," 0 0,",v," ",V[1],"A",n,",",n," 0 ",g^so(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-g," ",Z[1],"A",Y,",",Y," 0 0,",v," ",Z[0]):N.push("L",V[0],"A",Y,",",Y," 0 0,",v," ",Z[0])}else N.push("L",_,",",w)}else N.push("M",y,",",M),null!=x&&N.push("A",l,",",l," 0 ",C,",",g," ",x,",",b),N.push("L",_,",",w),null!=S&&N.push("A",n,",",n," 0 ",q,",",1-g," ",S,",",k);return N.push("Z"),N.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=io,r=oo,u=uo,i=kl,o=ao,a=co,c=lo;return n.innerRadius=function(t){return arguments.length?(e=Et(t),n):e},n.outerRadius=function(t){return arguments.length?(r=Et(t),n):r},n.cornerRadius=function(t){return arguments.length?(u=Et(t),n):u},n.padRadius=function(t){return arguments.length?(i=t==kl?kl:Et(t),n):i},n.startAngle=function(t){return arguments.length?(o=Et(t),n):o},n.endAngle=function(t){return arguments.length?(a=Et(t),n):a},n.padAngle=function(t){return arguments.length?(c=Et(t),n):c},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+o.apply(this,arguments)+ +a.apply(this,arguments))/2-Ra;return[Math.cos(t)*n,Math.sin(t)*n]},n};var kl="auto";ta.svg.line=function(){return ho(y)};var El=ta.map({linear:go,"linear-closed":po,step:vo,"step-before":mo,"step-after":yo,basis:So,"basis-open":ko,"basis-closed":Eo,bundle:Ao,cardinal:bo,"cardinal-open":Mo,"cardinal-closed":xo,monotone:To});El.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Al=[0,2/3,1/3,0],Nl=[0,1/3,2/3,0],Cl=[0,1/6,2/3,1/6];ta.svg.line.radial=function(){var n=ho(Ro);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},mo.reverse=yo,yo.reverse=mo,ta.svg.area=function(){return Do(y)},ta.svg.area.radial=function(){var n=Do(Ro);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},ta.svg.chord=function(){function n(n,a){var c=t(this,i,n,a),l=t(this,o,n,a);return"M"+c.p0+r(c.r,c.p1,c.a1-c.a0)+(e(c,l)?u(c.r,c.p1,c.r,c.p0):u(c.r,c.p1,l.r,l.p0)+r(l.r,l.p1,l.a1-l.a0)+u(l.r,l.p1,c.r,c.p0))+"Z"}function t(n,t,e,r){var u=t.call(n,e,r),i=a.call(n,u,r),o=c.call(n,u,r)-Ra,s=l.call(n,u,r)-Ra;return{r:i,a0:o,a1:s,p0:[i*Math.cos(o),i*Math.sin(o)],p1:[i*Math.cos(s),i*Math.sin(s)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>qa)+",1 "+t}function u(n,t,e,r){return"Q 0,0 "+r}var i=mr,o=yr,a=Po,c=ao,l=co;return n.radius=function(t){return arguments.length?(a=Et(t),n):a},n.source=function(t){return arguments.length?(i=Et(t),n):i},n.target=function(t){return arguments.length?(o=Et(t),n):o},n.startAngle=function(t){return arguments.length?(c=Et(t),n):c},n.endAngle=function(t){return arguments.length?(l=Et(t),n):l},n},ta.svg.diagonal=function(){function n(n,u){var i=t.call(this,n,u),o=e.call(this,n,u),a=(i.y+o.y)/2,c=[i,{x:i.x,y:a},{x:o.x,y:a},o];return c=c.map(r),"M"+c[0]+"C"+c[1]+" "+c[2]+" "+c[3]}var t=mr,e=yr,r=Uo;return n.source=function(e){return arguments.length?(t=Et(e),n):t},n.target=function(t){return arguments.length?(e=Et(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},ta.svg.diagonal.radial=function(){var n=ta.svg.diagonal(),t=Uo,e=n.projection;return n.projection=function(n){return arguments.length?e(jo(t=n)):t},n},ta.svg.symbol=function(){function n(n,r){return(zl.get(t.call(this,n,r))||Oo)(e.call(this,n,r))}var t=Ho,e=Fo;return n.type=function(e){return arguments.length?(t=Et(e),n):t},n.size=function(t){return arguments.length?(e=Et(t),n):e},n};var zl=ta.map({circle:Oo,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Ll)),e=t*Ll;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/ql),e=t*ql/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/ql),e=t*ql/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});ta.svg.symbolTypes=zl.keys();var ql=Math.sqrt(3),Ll=Math.tan(30*Da);_a.transition=function(n){for(var t,e,r=Tl||++Ul,u=Xo(n),i=[],o=Rl||{time:Date.now(),ease:Su,delay:0,duration:250},a=-1,c=this.length;++a<c;){i.push(t=[]);for(var l=this[a],s=-1,f=l.length;++s<f;)(e=l[s])&&$o(e,s,u,r,o),t.push(e)}return Yo(i,u,r)},_a.interrupt=function(n){return this.each(null==n?Dl:Io(Xo(n)))};var Tl,Rl,Dl=Io(Xo()),Pl=[],Ul=0;Pl.call=_a.call,Pl.empty=_a.empty,Pl.node=_a.node,Pl.size=_a.size,ta.transition=function(n,t){return n&&n.transition?Tl?n.transition(t):n:ta.selection().transition(n)},ta.transition.prototype=Pl,Pl.select=function(n){var t,e,r,u=this.id,i=this.namespace,o=[];n=N(n);for(var a=-1,c=this.length;++a<c;){o.push(t=[]);for(var l=this[a],s=-1,f=l.length;++s<f;)(r=l[s])&&(e=n.call(r,r.__data__,s,a))?("__data__"in r&&(e.__data__=r.__data__),$o(e,s,i,u,r[i][u]),t.push(e)):t.push(null)}return Yo(o,i,u)},Pl.selectAll=function(n){var t,e,r,u,i,o=this.id,a=this.namespace,c=[];n=C(n);for(var l=-1,s=this.length;++l<s;)for(var f=this[l],h=-1,g=f.length;++h<g;)if(r=f[h]){i=r[a][o],e=n.call(r,r.__data__,h,l),c.push(t=[]);for(var p=-1,v=e.length;++p<v;)(u=e[p])&&$o(u,p,a,o,i),t.push(u)}return Yo(c,a,o)},Pl.filter=function(n){var t,e,r,u=[];"function"!=typeof n&&(n=O(n));for(var i=0,o=this.length;o>i;i++){u.push(t=[]);for(var e=this[i],a=0,c=e.length;c>a;a++)(r=e[a])&&n.call(r,r.__data__,a,i)&&t.push(r)}return Yo(u,this.namespace,this.id)},Pl.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(u){u[r][e].tween.set(n,t)})},Pl.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function u(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function i(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?Hu:mu,a=ta.ns.qualify(n);return Zo(this,"attr."+n,t,a.local?i:u)},Pl.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(u));return r&&function(n){this.setAttribute(u,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(u.space,u.local));return r&&function(n){this.setAttributeNS(u.space,u.local,r(n))}}var u=ta.ns.qualify(n);return this.tween("attr."+n,u.local?r:e)},Pl.style=function(n,e,r){function u(){this.style.removeProperty(n)}function i(e){return null==e?u:(e+="",function(){var u,i=t(this).getComputedStyle(this,null).getPropertyValue(n);return i!==e&&(u=mu(i,e),function(t){this.style.setProperty(n,u(t),r)})})}var o=arguments.length;if(3>o){if("string"!=typeof n){2>o&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Zo(this,"style."+n,e,i)},Pl.styleTween=function(n,e,r){function u(u,i){var o=e.call(this,u,i,t(this).getComputedStyle(this,null).getPropertyValue(n));return o&&function(t){this.style.setProperty(n,o(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,u)},Pl.text=function(n){return Zo(this,"text",n,Vo)},Pl.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Pl.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=ta.ease.apply(ta,arguments)),Y(this,function(r){r[e][t].ease=n}))},Pl.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,u,i){r[e][t].delay=+n.call(r,r.__data__,u,i)}:(n=+n,function(r){r[e][t].delay=n}))},Pl.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,u,i){r[e][t].duration=Math.max(1,n.call(r,r.__data__,u,i))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Pl.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var u=Rl,i=Tl;try{Tl=e,Y(this,function(t,u,i){Rl=t[r][e],n.call(t,t.__data__,u,i)})}finally{Rl=u,Tl=i}}else Y(this,function(u){var i=u[r][e];(i.event||(i.event=ta.dispatch("start","end","interrupt"))).on(n,t)});return this},Pl.transition=function(){for(var n,t,e,r,u=this.id,i=++Ul,o=this.namespace,a=[],c=0,l=this.length;l>c;c++){a.push(n=[]);for(var t=this[c],s=0,f=t.length;f>s;s++)(e=t[s])&&(r=e[o][u],$o(e,s,o,i,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Yo(a,o,i)},ta.svg.axis=function(){function n(n){n.each(function(){var n,l=ta.select(this),s=this.__chart__||e,f=this.__chart__=e.copy(),h=null==c?f.ticks?f.ticks.apply(f,a):f.domain():c,g=null==t?f.tickFormat?f.tickFormat.apply(f,a):y:t,p=l.selectAll(".tick").data(h,f),v=p.enter().insert("g",".domain").attr("class","tick").style("opacity",Ca),d=ta.transition(p.exit()).style("opacity",Ca).remove(),m=ta.transition(p.order()).style("opacity",1),M=Math.max(u,0)+o,x=Ui(f),b=l.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),ta.transition(b));v.append("line"),v.append("text");var w,S,k,E,A=v.select("line"),N=m.select("line"),C=p.select("text").text(g),z=v.select("text"),q=m.select("text"),L="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=Bo,w="x",k="y",S="x2",E="y2",C.attr("dy",0>L?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+L*i+"V0H"+x[1]+"V"+L*i)):(n=Wo,w="y",k="x",S="y2",E="x2",C.attr("dy",".32em").style("text-anchor",0>L?"end":"start"),_.attr("d","M"+L*i+","+x[0]+"H0V"+x[1]+"H"+L*i)),A.attr(E,L*u),z.attr(k,L*M),N.attr(S,0).attr(E,L*u),q.attr(w,0).attr(k,L*M),f.rangeBand){var T=f,R=T.rangeBand()/2;s=f=function(n){return T(n)+R}}else s.rangeBand?s=f:d.call(n,f,s);v.call(n,s,f),m.call(n,f,f)})}var t,e=ta.scale.linear(),r=jl,u=6,i=6,o=3,a=[10],c=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Fl?t+"":jl,n):r},n.ticks=function(){return arguments.length?(a=arguments,n):a},n.tickValues=function(t){return arguments.length?(c=t,n):c},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(u=+t,i=+arguments[e-1],n):u},n.innerTickSize=function(t){return arguments.length?(u=+t,n):u},n.outerTickSize=function(t){return arguments.length?(i=+t,n):i},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var jl="bottom",Fl={top:1,right:1,bottom:1,left:1};ta.svg.brush=function(){function n(t){t.each(function(){var t=ta.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",i).on("touchstart.brush",i),o=t.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=t.selectAll(".resize").data(v,y);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return Hl[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var c,f=ta.transition(t),h=ta.transition(o);l&&(c=Ui(l),h.attr("x",c[0]).attr("width",c[1]-c[0]),r(f)),s&&(c=Ui(s),h.attr("y",c[0]).attr("height",c[1]-c[0]),u(f)),e(f)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+f[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",f[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",f[1]-f[0])}function u(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function i(){function i(){32==ta.event.keyCode&&(C||(M=null,q[0]-=f[1],q[1]-=h[1],C=2),S())}function v(){32==ta.event.keyCode&&2==C&&(q[0]+=f[1],q[1]+=h[1],C=0,S())}function d(){var n=ta.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(ta.event.altKey?(M||(M=[(f[0]+f[1])/2,(h[0]+h[1])/2]),q[0]=f[+(n[0]<M[0])],q[1]=h[+(n[1]<M[1])]):M=null),A&&m(n,l,0)&&(r(k),t=!0),N&&m(n,s,1)&&(u(k),t=!0),t&&(e(k),w({type:"brush",mode:C?"move":"resize"}))}function m(n,t,e){var r,u,i=Ui(t),c=i[0],l=i[1],s=q[e],v=e?h:f,d=v[1]-v[0];return C&&(c-=s,l-=d+s),r=(e?p:g)?Math.max(c,Math.min(l,n[e])):n[e],C?u=(r+=s)+d:(M&&(s=Math.max(c,Math.min(l,2*M[e]-r))),r>s?(u=r,r=s):u=s),v[0]!=r||v[1]!=u?(e?a=null:o=null,v[0]=r,v[1]=u,!0):void 0}function y(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),ta.select("body").style("cursor",null),L.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=ta.select(ta.event.target),w=c.of(b,arguments),k=ta.select(b),E=_.datum(),A=!/^(n|s)$/.test(E)&&l,N=!/^(e|w)$/.test(E)&&s,C=_.classed("extent"),z=W(b),q=ta.mouse(b),L=ta.select(t(b)).on("keydown.brush",i).on("keyup.brush",v);if(ta.event.changedTouches?L.on("touchmove.brush",d).on("touchend.brush",y):L.on("mousemove.brush",d).on("mouseup.brush",y),k.interrupt().selectAll("*").interrupt(),C)q[0]=f[0]-q[0],q[1]=h[0]-q[1];else if(E){var T=+/w$/.test(E),R=+/^n/.test(E);x=[f[1-T]-q[0],h[1-R]-q[1]],q[0]=f[T],q[1]=h[R]}else ta.event.altKey&&(M=q.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),ta.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var o,a,c=E(n,"brushstart","brush","brushend"),l=null,s=null,f=[0,0],h=[0,0],g=!0,p=!0,v=Ol[0];return n.event=function(n){n.each(function(){var n=c.of(this,arguments),t={x:f,y:h,i:o,j:a},e=this.__chart__||t;this.__chart__=t,Tl?ta.select(this).transition().each("start.brush",function(){o=e.i,a=e.j,f=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=yu(f,t.x),r=yu(h,t.y);return o=a=null,function(u){f=t.x=e(u),h=t.y=r(u),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){o=t.i,a=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(l=t,v=Ol[!l<<1|!s],n):l},n.y=function(t){return arguments.length?(s=t,v=Ol[!l<<1|!s],n):s},n.clamp=function(t){return arguments.length?(l&&s?(g=!!t[0],p=!!t[1]):l?g=!!t:s&&(p=!!t),n):l&&s?[g,p]:l?g:s?p:null},n.extent=function(t){var e,r,u,i,c;return arguments.length?(l&&(e=t[0],r=t[1],s&&(e=e[0],r=r[0]),o=[e,r],l.invert&&(e=l(e),r=l(r)),e>r&&(c=e,e=r,r=c),(e!=f[0]||r!=f[1])&&(f=[e,r])),s&&(u=t[0],i=t[1],l&&(u=u[1],i=i[1]),a=[u,i],s.invert&&(u=s(u),i=s(i)),u>i&&(c=u,u=i,i=c),(u!=h[0]||i!=h[1])&&(h=[u,i])),n):(l&&(o?(e=o[0],r=o[1]):(e=f[0],r=f[1],l.invert&&(e=l.invert(e),r=l.invert(r)),e>r&&(c=e,e=r,r=c))),s&&(a?(u=a[0],i=a[1]):(u=h[0],i=h[1],s.invert&&(u=s.invert(u),i=s.invert(i)),u>i&&(c=u,u=i,i=c))),l&&s?[[e,u],[r,i]]:l?[e,r]:s&&[u,i])},n.clear=function(){return n.empty()||(f=[0,0],h=[0,0],o=a=null),n},n.empty=function(){return!!l&&f[0]==f[1]||!!s&&h[0]==h[1]},ta.rebind(n,c,"on")};var Hl={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Ol=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Il=ac.format=gc.timeFormat,Yl=Il.utc,Zl=Yl("%Y-%m-%dT%H:%M:%S.%LZ");Il.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?Jo:Zl,Jo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},Jo.toString=Zl.toString,ac.second=Ft(function(n){return new cc(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ac.seconds=ac.second.range,ac.seconds.utc=ac.second.utc.range,ac.minute=Ft(function(n){return new cc(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ac.minutes=ac.minute.range,ac.minutes.utc=ac.minute.utc.range,ac.hour=Ft(function(n){var t=n.getTimezoneOffset()/60;return new cc(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ac.hours=ac.hour.range,ac.hours.utc=ac.hour.utc.range,ac.month=Ft(function(n){return n=ac.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ac.months=ac.month.range,ac.months.utc=ac.month.utc.range;var Vl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Xl=[[ac.second,1],[ac.second,5],[ac.second,15],[ac.second,30],[ac.minute,1],[ac.minute,5],[ac.minute,15],[ac.minute,30],[ac.hour,1],[ac.hour,3],[ac.hour,6],[ac.hour,12],[ac.day,1],[ac.day,2],[ac.week,1],[ac.month,1],[ac.month,3],[ac.year,1]],$l=Il.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",Ne]]),Bl={range:function(n,t,e){return ta.range(Math.ceil(n/e)*e,+t,e).map(Ko)},floor:y,ceil:y};Xl.year=ac.year,ac.scale=function(){return Go(ta.scale.linear(),Xl,$l)};var Wl=Xl.map(function(n){return[n[0].utc,n[1]]}),Jl=Yl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",Ne]]);Wl.year=ac.year.utc,ac.scale.utc=function(){return Go(ta.scale.linear(),Wl,Jl)},ta.text=At(function(n){return n.responseText}),ta.json=function(n,t){return Nt(n,"application/json",Qo,t)},ta.html=function(n,t){return Nt(n,"text/html",na,t)},ta.xml=At(function(n){return n.responseXML}),"function"==typeof define&&define.amd?define(ta):"object"==typeof module&&module.exports&&(module.exports=ta),this.d3=ta}();
diff --git a/modules/http/static/d3.spdx b/modules/http/static/d3.spdx
new file mode 100644
index 0000000..95477b1
--- /dev/null
+++ b/modules/http/static/d3.spdx
@@ -0,0 +1,12 @@
+SPDXVersion: SPDX-2.1
+DataLicense: CC0-1.0
+SPDXID: SPDXRef-DOCUMENT
+DocumentName: d3js
+DocumentNamespace: http://spdx.org/spdxdocs/spdx-v2.1-d849c611-6d18-4c73-9318-f01eab74a036
+
+PackageName: d3js
+PackageVersion: 3.5.6
+PackageDownloadLocation: https://github.com/d3/d3/releases/download/v3.5.6/d3.zip#d3.min.js
+PackageChecksum: SHA256: 3865a5ee7b9f91126f2ef1121a7635e57bd820c9dbc384c2c48626b93a13d3f6
+PackageOriginator: Person: Michael Bostock (mike@ocks.org)
+PackageLicenseDeclared: BSD-3-Clause
diff --git a/modules/http/static/datamaps.world.min.js b/modules/http/static/datamaps.world.min.js
new file mode 100644
index 0000000..39e0e0a
--- /dev/null
+++ b/modules/http/static/datamaps.world.min.js
@@ -0,0 +1,3 @@
+/* SPDX-License-Identifier: MIT */
+!function(){function a(a,b,c){return this.svg=m.select(a).append("svg").attr("width",c||a.offsetWidth).attr("data-width",c||a.offsetWidth).attr("class","datamap").attr("height",b||a.offsetHeight).style("overflow","hidden"),this.options.responsive&&(m.select(this.options.element).style({position:"relative","padding-bottom":"60%"}),m.select(this.options.element).select("svg").style({position:"absolute",width:"100%",height:"100%"}),m.select(this.options.element).select("svg").select("g").selectAll("path").style("vector-effect","non-scaling-stroke")),this.svg}function b(a,b){var c,d,e=b.width||a.offsetWidth,f=b.height||a.offsetHeight,g=this.svg;return b&&"undefined"==typeof b.scope&&(b.scope="world"),"usa"===b.scope?c=m.geo.albersUsa().scale(e).translate([e/2,f/2]):"world"===b.scope&&(c=m.geo[b.projection]().scale((e+1)/2/Math.PI).translate([e/2,f/("mercator"===b.projection?1.45:1.8)])),"orthographic"===b.projection&&(g.append("defs").append("path").datum({type:"Sphere"}).attr("id","sphere").attr("d",d),g.append("use").attr("class","stroke").attr("xlink:href","#sphere"),g.append("use").attr("class","fill").attr("xlink:href","#sphere"),c.scale(250).clipAngle(90).rotate(b.projectionConfig.rotation)),d=m.geo.path().projection(c),{path:d,projection:c}}function c(){m.select(".datamaps-style-block").empty()&&m.select("head").append("style").attr("class","datamaps-style-block").html('.datamap path.datamaps-graticule { fill: none; stroke: #777; stroke-width: 0.5px; stroke-opacity: .5; pointer-events: none; } .datamap .labels {pointer-events: none;} .datamap path {stroke: #FFFFFF; stroke-width: 1px;} .datamaps-legend dt, .datamaps-legend dd { float: left; margin: 0 3px 0 0;} .datamaps-legend dd {width: 20px; margin-right: 6px; border-radius: 3px;} .datamaps-legend {padding-bottom: 20px; z-index: 1001; position: absolute; left: 4px; font-size: 12px; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;} .datamaps-hoverover {display: none; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } .hoverinfo {padding: 4px; border-radius: 1px; background-color: #FFF; box-shadow: 1px 1px 5px #CCC; font-size: 12px; border: 1px solid #CCC; } .hoverinfo hr {border:1px dotted #CCC; }')}function d(a){var b=this.options.fills,c=this.options.data||{},d=this.options.geographyConfig,e=this.svg.select("g.datamaps-subunits");e.empty()&&(e=this.addLayer("datamaps-subunits",null,!0));var f=n.feature(a,a.objects[this.options.scope]).features;d.hideAntarctica&&(f=f.filter(function(a){return"ATA"!==a.id}));var g=e.selectAll("path.datamaps-subunit").data(f);g.enter().append("path").attr("d",this.path).attr("class",function(a){return"datamaps-subunit "+a.id}).attr("data-info",function(a){return JSON.stringify(c[a.id])}).style("fill",function(a){var d;return c[a.id]&&(d=b[c[a.id].fillKey]),d||b.defaultFill}).style("stroke-width",d.borderWidth).style("stroke",d.borderColor)}function e(){function a(){this.parentNode.appendChild(this)}var b=this.svg,c=this,d=this.options.geographyConfig;(d.highlightOnHover||d.popupOnHover)&&b.selectAll(".datamaps-subunit").on("mouseover",function(e){var f=m.select(this);if(d.highlightOnHover){var g={fill:f.style("fill"),stroke:f.style("stroke"),"stroke-width":f.style("stroke-width"),"fill-opacity":f.style("fill-opacity")};f.style("fill",d.highlightFillColor).style("stroke",d.highlightBorderColor).style("stroke-width",d.highlightBorderWidth).style("fill-opacity",d.highlightFillOpacity).attr("data-previousAttributes",JSON.stringify(g)),/((MSIE)|(Trident))/.test||a.call(this)}d.popupOnHover&&c.updatePopup(f,e,d,b)}).on("mouseout",function(){var a=m.select(this);if(d.highlightOnHover){var b=JSON.parse(a.attr("data-previousAttributes"));for(var c in b)a.style(c,b[c])}a.on("mousemove",null),m.selectAll(".datamaps-hoverover").style("display","none")})}function f(a,b){if(b=b||{},this.options.fills){var c="<dl>",d="";b.legendTitle&&(c="<h2>"+b.legendTitle+"</h2>"+c);for(var e in this.options.fills){if("defaultFill"===e){if(!b.defaultFillName)continue;d=b.defaultFillName}else d=b.labels&&b.labels[e]?b.labels[e]:e+": ";c+="<dt>"+d+"</dt>",c+='<dd style="background-color:'+this.options.fills[e]+'">&nbsp;</dd>'}c+="</dl>",m.select(this.options.element).append("div").attr("class","datamaps-legend").html(c)}}function g(){var a=m.geo.graticule();this.svg.insert("path",".datamaps-subunits").datum(a).attr("class","datamaps-graticule").attr("d",this.path)}function h(a,b,c){var d=this;if(this.svg,!b||b&&!b.slice)throw"Datamaps Error - arcs must be an array";"undefined"==typeof c&&(c=o.arcConfig);var e=a.selectAll("path.datamaps-arc").data(b,JSON.stringify),f=m.geo.path().projection(d.projection),g=m.geo.greatArc().source(function(a){return[a.origin.longitude,a.origin.latitude]}).target(function(a){return[a.destination.longitude,a.destination.latitude]});e.enter().append("svg:path").attr("class","datamaps-arc").style("stroke-linecap","round").style("stroke",function(a){return a.options&&a.options.strokeColor?a.options.strokeColor:c.strokeColor}).style("fill","none").style("stroke-width",function(a){return a.options&&a.options.strokeWidth?a.options.strokeWidth:c.strokeWidth}).attr("d",function(a){var b=d.latLngToXY(a.origin.latitude,a.origin.longitude),e=d.latLngToXY(a.destination.latitude,a.destination.longitude),h=[(b[0]+e[0])/2,(b[1]+e[1])/2];return c.greatArc?f(g(a)):"M"+b[0]+","+b[1]+"S"+(h[0]+50*c.arcSharpness)+","+(h[1]-75*c.arcSharpness)+","+e[0]+","+e[1]}).transition().delay(100).style("fill",function(){var a=this.getTotalLength();return this.style.transition=this.style.WebkitTransition="none",this.style.strokeDasharray=a+" "+a,this.style.strokeDashoffset=a,this.getBoundingClientRect(),this.style.transition=this.style.WebkitTransition="stroke-dashoffset "+c.animationSpeed+"ms ease-out",this.style.strokeDashoffset="0","none"}),e.exit().transition().style("opacity",0).remove()}function i(a,b){var c=this;b=b||{};var d=this.projection([-67.707617,42.722131]);this.svg.selectAll(".datamaps-subunit").attr("data-foo",function(e){var f=c.path.centroid(e),g=7.5,h=5;["FL","KY","MI"].indexOf(e.id)>-1&&(g=-2.5),"NY"===e.id&&(g=-1),"MI"===e.id&&(h=18),"LA"===e.id&&(g=13);var i,j;i=f[0]-g,j=f[1]+h;var k=["VT","NH","MA","RI","CT","NJ","DE","MD","DC"].indexOf(e.id);if(k>-1){var l=d[1];i=d[0],j=l+k*(2+(b.fontSize||12)),a.append("line").attr("x1",i-3).attr("y1",j-5).attr("x2",f[0]).attr("y2",f[1]).style("stroke",b.labelColor||"#000").style("stroke-width",b.lineWidth||1)}return a.append("text").attr("x",i).attr("y",j).style("font-size",(b.fontSize||10)+"px").style("font-family",b.fontFamily||"Verdana").style("fill",b.labelColor||"#000").text(e.id),"bar"})}function j(a,b,c){function d(a){return"undefined"!=typeof a&&"undefined"!=typeof a.latitude&&"undefined"!=typeof a.longitude}var e=this,f=this.options.fills,g=this.svg;if(!b||b&&!b.slice)throw"Datamaps Error - bubbles must be an array";var h=a.selectAll("circle.datamaps-bubble").data(b,JSON.stringify);h.enter().append("svg:circle").attr("class","datamaps-bubble").attr("cx",function(a){var b;return d(a)?b=e.latLngToXY(a.latitude,a.longitude):a.centered&&(b=e.path.centroid(g.select("path."+a.centered).data()[0])),b?b[0]:void 0}).attr("cy",function(a){var b;return d(a)?b=e.latLngToXY(a.latitude,a.longitude):a.centered&&(b=e.path.centroid(g.select("path."+a.centered).data()[0])),b?b[1]:void 0}).attr("r",0).attr("data-info",function(a){return JSON.stringify(a)}).style("stroke",function(a){return"undefined"!=typeof a.borderColor?a.borderColor:c.borderColor}).style("stroke-width",function(a){return"undefined"!=typeof a.borderWidth?a.borderWidth:c.borderWidth}).style("fill-opacity",function(a){return"undefined"!=typeof a.fillOpacity?a.fillOpacity:c.fillOpacity}).style("fill",function(a){var b=f[a.fillKey];return b||f.defaultFill}).on("mouseover",function(a){var b=m.select(this);if(c.highlightOnHover){var d={fill:b.style("fill"),stroke:b.style("stroke"),"stroke-width":b.style("stroke-width"),"fill-opacity":b.style("fill-opacity")};b.style("fill",c.highlightFillColor).style("stroke",c.highlightBorderColor).style("stroke-width",c.highlightBorderWidth).style("fill-opacity",c.highlightFillOpacity).attr("data-previousAttributes",JSON.stringify(d))}c.popupOnHover&&e.updatePopup(b,a,c,g)}).on("mouseout",function(){var a=m.select(this);if(c.highlightOnHover){var b=JSON.parse(a.attr("data-previousAttributes"));for(var d in b)a.style(d,b[d])}m.selectAll(".datamaps-hoverover").style("display","none")}).transition().duration(400).attr("r",function(a){return a.radius}),h.exit().transition().delay(c.exitDelay).attr("r",0).remove()}function k(a){return Array.prototype.slice.call(arguments,1).forEach(function(b){if(b)for(var c in b)null==a[c]&&(a[c]=b[c])}),a}function l(b){if("undefined"==typeof m||"undefined"==typeof n)throw new Error("Include d3.js (v3.0.3 or greater) and topojson on this page before creating a new map");return this.options=k(b,o),this.options.geographyConfig=k(b.geographyConfig,o.geographyConfig),this.options.projectionConfig=k(b.projectionConfig,o.projectionConfig),this.options.bubblesConfig=k(b.bubblesConfig,o.bubblesConfig),this.options.arcConfig=k(b.arcConfig,o.arcConfig),m.select(this.options.element).select("svg").length>0&&a.call(this,this.options.element,this.options.height,this.options.width),this.addPlugin("bubbles",j),this.addPlugin("legend",f),this.addPlugin("arc",h),this.addPlugin("labels",i),this.addPlugin("graticule",g),this.options.disableDefaultStyles||c(),this.draw()}var m=window.d3,n=window.topojson,o={scope:"world",responsive:!1,setProjection:b,projection:"equirectangular",dataType:"json",done:function(){},fills:{defaultFill:"#ABDDA4"},geographyConfig:{dataUrl:null,hideAntarctica:!0,borderWidth:1,borderColor:"#FDFDFD",popupTemplate:function(a){return'<div class="hoverinfo"><strong>'+a.properties.name+"</strong></div>"},popupOnHover:!0,highlightOnHover:!0,highlightFillColor:"#FC8D59",highlightBorderColor:"rgba(250, 15, 160, 0.2)",highlightBorderWidth:2},projectionConfig:{rotation:[97,0]},bubblesConfig:{borderWidth:2,borderColor:"#FFFFFF",popupOnHover:!0,popupTemplate:function(a,b){return'<div class="hoverinfo"><strong>'+b.name+"</strong></div>"},fillOpacity:.75,animate:!0,highlightOnHover:!0,highlightFillColor:"#FC8D59",highlightBorderColor:"rgba(250, 15, 160, 0.2)",highlightBorderWidth:2,highlightFillOpacity:.85,exitDelay:100},arcConfig:{strokeColor:"#DD1C77",strokeWidth:1,arcSharpness:1,animationSpeed:600}};l.prototype.resize=function(){var a=this,b=a.options;if(b.responsive){var c="-webkit-transform"in document.body.style?"-webkit-":"-moz-transform"in document.body.style?"-moz-":"-ms-transform"in document.body.style?"-ms-":"",d=b.element.clientWidth,e=m.select(b.element).select("svg").attr("data-width");m.select(b.element).select("svg").selectAll("g").style(c+"transform","scale("+d/e+")")}},l.prototype.draw=function(){function a(a){b.options.dataUrl&&m[b.options.dataType](b.options.dataUrl,function(a){if("csv"===b.options.dataType&&a&&a.slice){for(var c={},d=0;d<a.length;d++)c[a[d].id]=a[d];a=c}Datamaps.prototype.updateChoropleth.call(b,a)}),d.call(b,a),e.call(b),(b.options.geographyConfig.popupOnHover||b.options.bubblesConfig.popupOnHover)&&(hoverover=m.select(b.options.element).append("div").attr("class","datamaps-hoverover").style("z-index",10001).style("position","absolute")),b.options.done(b)}var b=this,c=b.options,f=c.setProjection.apply(b,[c.element,c]);return this.path=f.path,this.projection=f.projection,c.geographyConfig.dataUrl?m.json(c.geographyConfig.dataUrl,function(c,d){if(c)throw new Error(c);b.customTopo=d,a(d)}):a(this[c.scope+"Topo"]||c.geographyConfig.dataJson),this},l.prototype.worldTopo={type:"Topology",objects:{world:{type:"GeometryCollection",geometries:[{type:"Polygon",properties:{name:"Afghanistan"},id:"AFG",arcs:[[0,1,2,3,4,5]]},{type:"MultiPolygon",properties:{name:"Angola"},id:"AGO",arcs:[[[6,7,8,9]],[[10,11,12]]]},{type:"Polygon",properties:{name:"Albania"},id:"ALB",arcs:[[13,14,15,16,17]]},{type:"Polygon",properties:{name:"United Arab Emirates"},id:"ARE",arcs:[[18,19,20,21,22]]},{type:"MultiPolygon",properties:{name:"Argentina"},id:"ARG",arcs:[[[23,24]],[[25,26,27,28,29,30]]]},{type:"Polygon",properties:{name:"Armenia"},id:"ARM",arcs:[[31,32,33,34,35]]},{type:"MultiPolygon",properties:{name:"Antarctica"},id:"ATA",arcs:[[[36]],[[37]],[[38]],[[39]],[[40]],[[41]],[[42]],[[43]]]},{type:"Polygon",properties:{name:"French Southern and Antarctic Lands"},id:"ATF",arcs:[[44]]},{type:"MultiPolygon",properties:{name:"Australia"},id:"AUS",arcs:[[[45]],[[46]]]},{type:"Polygon",properties:{name:"Austria"},id:"AUT",arcs:[[47,48,49,50,51,52,53]]},{type:"MultiPolygon",properties:{name:"Azerbaijan"},id:"AZE",arcs:[[[54,-35]],[[55,56,-33,57,58]]]},{type:"Polygon",properties:{name:"Burundi"},id:"BDI",arcs:[[59,60,61]]},{type:"Polygon",properties:{name:"Belgium"},id:"BEL",arcs:[[62,63,64,65,66]]},{type:"Polygon",properties:{name:"Benin"},id:"BEN",arcs:[[67,68,69,70,71]]},{type:"Polygon",properties:{name:"Burkina Faso"},id:"BFA",arcs:[[72,73,74,-70,75,76]]},{type:"Polygon",properties:{name:"Bangladesh"},id:"BGD",arcs:[[77,78,79]]},{type:"Polygon",properties:{name:"Bulgaria"},id:"BGR",arcs:[[80,81,82,83,84,85]]},{type:"MultiPolygon",properties:{name:"The Bahamas"},id:"BHS",arcs:[[[86]],[[87]],[[88]]]},{type:"Polygon",properties:{name:"Bosnia and Herzegovina"},id:"BIH",arcs:[[89,90,91]]},{type:"Polygon",properties:{name:"Belarus"},id:"BLR",arcs:[[92,93,94,95,96]]},{type:"Polygon",properties:{name:"Belize"},id:"BLZ",arcs:[[97,98,99]]},{type:"Polygon",properties:{name:"Bolivia"},id:"BOL",arcs:[[100,101,102,103,-31]]},{type:"Polygon",properties:{name:"Brazil"},id:"BRA",arcs:[[-27,104,-103,105,106,107,108,109,110,111,112]]},{type:"Polygon",properties:{name:"Brunei"},id:"BRN",arcs:[[113,114]]},{type:"Polygon",properties:{name:"Bhutan"},id:"BTN",arcs:[[115,116]]},{type:"Polygon",properties:{name:"Botswana"},id:"BWA",arcs:[[117,118,119,120]]},{type:"Polygon",properties:{name:"Central African Republic"},id:"CAF",arcs:[[121,122,123,124,125,126,127]]},{type:"MultiPolygon",properties:{name:"Canada"},id:"CAN",arcs:[[[128]],[[129]],[[130]],[[131]],[[132]],[[133]],[[134]],[[135]],[[136]],[[137]],[[138,139,140,141]],[[142]],[[143]],[[144]],[[145]],[[146]],[[147]],[[148]],[[149]],[[150]],[[151]],[[152]],[[153]],[[154]],[[155]],[[156]],[[157]],[[158]],[[159]],[[160]]]},{type:"Polygon",properties:{name:"Switzerland"},id:"CHE",arcs:[[-51,161,162,163]]},{type:"MultiPolygon",properties:{name:"Chile"},id:"CHL",arcs:[[[-24,164]],[[-30,165,166,-101]]]},{type:"MultiPolygon",properties:{name:"China"},id:"CHN",arcs:[[[167]],[[168,169,170,171,172,173,-117,174,175,176,177,-4,178,179,180,181,182,183]]]},{type:"Polygon",properties:{name:"Ivory Coast"},id:"CIV",arcs:[[184,185,186,187,-73,188]]},{type:"Polygon",properties:{name:"Cameroon"},id:"CMR",arcs:[[189,190,191,192,193,194,-128,195]]},{type:"Polygon",properties:{name:"Democratic Republic of the Congo"},id:"COD",arcs:[[196,197,-60,198,199,-10,200,-13,201,-126,202]]},{type:"Polygon",properties:{name:"Republic of the Congo"},id:"COG",arcs:[[-12,203,204,-196,-127,-202]]},{type:"Polygon",properties:{name:"Colombia"},id:"COL",arcs:[[205,206,207,208,209,-107,210]]},{type:"Polygon",properties:{name:"Costa Rica"},id:"CRI",arcs:[[211,212,213,214]]},{type:"Polygon",properties:{name:"Cuba"},id:"CUB",arcs:[[215]]},{type:"Polygon",properties:{name:"Northern Cyprus"},id:"-99",arcs:[[216,217]]},{type:"Polygon",properties:{name:"Cyprus"},id:"CYP",arcs:[[218,-218]]},{type:"Polygon",properties:{name:"Czech Republic"},id:"CZE",arcs:[[-53,219,220,221]]},{type:"Polygon",properties:{name:"Germany"},id:"DEU",arcs:[[222,223,-220,-52,-164,224,225,-64,226,227,228]]},{type:"Polygon",properties:{name:"Djibouti"},id:"DJI",arcs:[[229,230,231,232]]},{type:"MultiPolygon",properties:{name:"Denmark"},id:"DNK",arcs:[[[233]],[[-229,234]]]},{type:"Polygon",properties:{name:"Dominican Republic"},id:"DOM",arcs:[[235,236]]},{type:"Polygon",properties:{name:"Algeria"},id:"DZA",arcs:[[237,238,239,240,241,242,243,244]]},{type:"Polygon",properties:{name:"Ecuador"},id:"ECU",arcs:[[245,-206,246]]},{type:"Polygon",properties:{name:"Egypt"},id:"EGY",arcs:[[247,248,249,250,251]]},{type:"Polygon",properties:{name:"Eritrea"},id:"ERI",arcs:[[252,253,254,-233]]},{type:"Polygon",properties:{name:"Spain"},id:"ESP",arcs:[[255,256,257,258]]},{type:"Polygon",properties:{name:"Estonia"},id:"EST",arcs:[[259,260,261]]},{type:"Polygon",properties:{name:"Ethiopia"},id:"ETH",arcs:[[-232,262,263,264,265,266,267,-253]]},{type:"Polygon",properties:{name:"Finland"},id:"FIN",arcs:[[268,269,270,271]]},{type:"MultiPolygon",properties:{name:"Fiji"},id:"FJI",arcs:[[[272]],[[273,274]],[[275,-275]]]},{type:"Polygon",properties:{name:"Falkland Islands"},id:"FLK",arcs:[[276]]},{type:"MultiPolygon",properties:{name:"France"},id:"FRA",arcs:[[[277]],[[278,-225,-163,279,280,-257,281,-66]]]},{type:"Polygon",properties:{name:"French Guiana"},id:"GUF",arcs:[[282,283,284,285,-111]]},{type:"Polygon",properties:{name:"Gabon"},id:"GAB",arcs:[[286,287,-190,-205]]},{type:"MultiPolygon",properties:{name:"United Kingdom"},id:"GBR",arcs:[[[288,289]],[[290]]]},{type:"Polygon",properties:{name:"Georgia"},id:"GEO",arcs:[[291,292,-58,-32,293]]},{type:"Polygon",properties:{name:"Ghana"},id:"GHA",arcs:[[294,-189,-77,295]]},{type:"Polygon",properties:{name:"Guinea"},id:"GIN",arcs:[[296,297,298,299,300,301,-187]]},{type:"Polygon",properties:{name:"Gambia"},id:"GMB",arcs:[[302,303]]},{type:"Polygon",properties:{name:"Guinea Bissau"},id:"GNB",arcs:[[304,305,-300]]},{type:"Polygon",properties:{name:"Equatorial Guinea"},id:"GNQ",arcs:[[306,-191,-288]]},{type:"MultiPolygon",properties:{name:"Greece"},id:"GRC",arcs:[[[307]],[[308,-15,309,-84,310]]]},{type:"Polygon",properties:{name:"Greenland"},id:"GRL",arcs:[[311]]},{type:"Polygon",properties:{name:"Guatemala"},id:"GTM",arcs:[[312,313,-100,314,315,316]]},{type:"Polygon",properties:{name:"Guyana"},id:"GUY",arcs:[[317,318,-109,319]]},{type:"Polygon",properties:{name:"Honduras"},id:"HND",arcs:[[320,321,-316,322,323]]},{type:"Polygon",properties:{name:"Croatia"},id:"HRV",arcs:[[324,-92,325,326,327,328]]},{type:"Polygon",properties:{name:"Haiti"},id:"HTI",arcs:[[-237,329]]},{type:"Polygon",properties:{name:"Hungary"},id:"HUN",arcs:[[-48,330,331,332,333,-329,334]]},{type:"MultiPolygon",properties:{name:"Indonesia"},id:"IDN",arcs:[[[335]],[[336,337]],[[338]],[[339]],[[340]],[[341]],[[342]],[[343]],[[344,345]],[[346]],[[347]],[[348,349]],[[350]]]},{type:"Polygon",properties:{name:"India"},id:"IND",arcs:[[-177,351,-175,-116,-174,352,-80,353,354]]},{type:"Polygon",properties:{name:"Ireland"},id:"IRL",arcs:[[355,-289]]},{type:"Polygon",properties:{name:"Iran"},id:"IRN",arcs:[[356,-6,357,358,359,360,-55,-34,-57,361]]},{type:"Polygon",properties:{name:"Iraq"},id:"IRQ",arcs:[[362,363,364,365,366,367,-360]]},{type:"Polygon",properties:{name:"Iceland"},id:"ISL",arcs:[[368]]},{type:"Polygon",properties:{name:"Israel"},id:"ISR",arcs:[[369,370,371,-252,372,373,374]]},{type:"MultiPolygon",properties:{name:"Italy"},id:"ITA",arcs:[[[375]],[[376]],[[377,378,-280,-162,-50]]]},{type:"Polygon",properties:{name:"Jamaica"},id:"JAM",arcs:[[379]]},{type:"Polygon",properties:{name:"Jordan"},id:"JOR",arcs:[[-370,380,-366,381,382,-372,383]]},{type:"MultiPolygon",properties:{name:"Japan"},id:"JPN",arcs:[[[384]],[[385]],[[386]]]},{type:"Polygon",properties:{name:"Kazakhstan"},id:"KAZ",arcs:[[387,388,389,390,-181,391]]},{type:"Polygon",properties:{name:"Kenya"},id:"KEN",arcs:[[392,393,394,395,-265,396]]},{type:"Polygon",properties:{name:"Kyrgyzstan"},id:"KGZ",arcs:[[-392,-180,397,398]]},{type:"Polygon",properties:{name:"Cambodia"},id:"KHM",arcs:[[399,400,401,402]]},{type:"Polygon",properties:{name:"South Korea"},id:"KOR",arcs:[[403,404]]},{type:"Polygon",properties:{name:"Kosovo"},id:"-99",arcs:[[-18,405,406,407]]},{type:"Polygon",properties:{name:"Kuwait"},id:"KWT",arcs:[[408,409,-364]]},{type:"Polygon",properties:{name:"Laos"},id:"LAO",arcs:[[410,411,-172,412,-401]]},{type:"Polygon",properties:{name:"Lebanon"},id:"LBN",arcs:[[-374,413,414]]},{type:"Polygon",properties:{name:"Liberia"},id:"LBR",arcs:[[415,416,-297,-186]]},{type:"Polygon",properties:{name:"Libya"},id:"LBY",arcs:[[417,-245,418,419,-250,420,421]]},{type:"Polygon",properties:{name:"Sri Lanka"},id:"LKA",arcs:[[422]]},{type:"Polygon",properties:{name:"Lesotho"},id:"LSO",arcs:[[423]]},{type:"Polygon",properties:{name:"Lithuania"},id:"LTU",arcs:[[424,425,426,-93,427]]},{type:"Polygon",properties:{name:"Luxembourg"},id:"LUX",arcs:[[-226,-279,-65]]},{type:"Polygon",properties:{name:"Latvia"},id:"LVA",arcs:[[428,-262,429,-94,-427]]},{type:"Polygon",properties:{name:"Morocco"},id:"MAR",arcs:[[-242,430,431]]},{type:"Polygon",properties:{name:"Moldova"},id:"MDA",arcs:[[432,433]]},{type:"Polygon",properties:{name:"Madagascar"},id:"MDG",arcs:[[434]]},{type:"Polygon",properties:{name:"Mexico"},id:"MEX",arcs:[[435,-98,-314,436,437]]},{type:"Polygon",properties:{name:"Macedonia"},id:"MKD",arcs:[[-408,438,-85,-310,-14]]},{type:"Polygon",properties:{name:"Mali"},id:"MLI",arcs:[[439,-239,440,-74,-188,-302,441]]},{type:"Polygon",properties:{name:"Myanmar"},id:"MMR",arcs:[[442,-78,-353,-173,-412,443]]},{type:"Polygon",properties:{name:"Montenegro"},id:"MNE",arcs:[[444,-326,-91,445,-406,-17]]},{type:"Polygon",properties:{name:"Mongolia"},id:"MNG",arcs:[[446,-183]]},{type:"Polygon",properties:{name:"Mozambique"},id:"MOZ",arcs:[[447,448,449,450,451,452,453,454]]},{type:"Polygon",properties:{name:"Mauritania"},id:"MRT",arcs:[[455,456,457,-240,-440]]},{type:"Polygon",properties:{name:"Malawi"},id:"MWI",arcs:[[-455,458,459]]},{type:"MultiPolygon",properties:{name:"Malaysia"},id:"MYS",arcs:[[[460,461]],[[-349,462,-115,463]]]},{type:"Polygon",properties:{name:"Namibia"},id:"NAM",arcs:[[464,-8,465,-119,466]]},{type:"Polygon",properties:{name:"New Caledonia"},id:"NCL",arcs:[[467]]},{type:"Polygon",properties:{name:"Niger"},id:"NER",arcs:[[-75,-441,-238,-418,468,-194,469,-71]]},{type:"Polygon",properties:{name:"Nigeria"},id:"NGA",arcs:[[470,-72,-470,-193]]},{type:"Polygon",properties:{name:"Nicaragua"},id:"NIC",arcs:[[471,-324,472,-213]]},{type:"Polygon",properties:{name:"Netherlands"},id:"NLD",arcs:[[-227,-63,473]]},{type:"MultiPolygon",properties:{name:"Norway"},id:"NOR",arcs:[[[474,-272,475,476]],[[477]],[[478]],[[479]]]},{type:"Polygon",properties:{name:"Nepal"},id:"NPL",arcs:[[-352,-176]]},{type:"MultiPolygon",properties:{name:"New Zealand"},id:"NZL",arcs:[[[480]],[[481]]]},{type:"MultiPolygon",properties:{name:"Oman"},id:"OMN",arcs:[[[482,483,-22,484]],[[-20,485]]]},{type:"Polygon",properties:{name:"Pakistan"},id:"PAK",arcs:[[-178,-355,486,-358,-5]]},{type:"Polygon",properties:{name:"Panama"},id:"PAN",arcs:[[487,-215,488,-208]]},{type:"Polygon",properties:{name:"Peru"},id:"PER",arcs:[[-167,489,-247,-211,-106,-102]]},{type:"MultiPolygon",properties:{name:"Philippines"},id:"PHL",arcs:[[[490]],[[491]],[[492]],[[493]],[[494]],[[495]],[[496]]]},{type:"MultiPolygon",properties:{name:"Papua New Guinea"},id:"PNG",arcs:[[[497]],[[498]],[[-345,499]],[[500]]]},{type:"Polygon",properties:{name:"Poland"},id:"POL",arcs:[[-224,501,502,-428,-97,503,504,-221]]},{type:"Polygon",properties:{name:"Puerto Rico"},id:"PRI",arcs:[[505]]},{type:"Polygon",properties:{name:"North Korea"},id:"PRK",arcs:[[506,507,-405,508,-169]]},{type:"Polygon",properties:{name:"Portugal"},id:"PRT",arcs:[[-259,509]]},{type:"Polygon",properties:{name:"Paraguay"},id:"PRY",arcs:[[-104,-105,-26]]},{type:"Polygon",properties:{name:"Qatar"},id:"QAT",arcs:[[510,511]]},{type:"Polygon",properties:{name:"Romania"},id:"ROU",arcs:[[512,-434,513,514,-81,515,-333]]},{type:"MultiPolygon",properties:{name:"Russia"},id:"RUS",arcs:[[[516]],[[-503,517,-425]],[[518,519]],[[520]],[[521]],[[522]],[[523]],[[524]],[[525]],[[526,-507,-184,-447,-182,-391,527,-59,-293,528,529,-95,-430,-261,530,-269,-475,531,-520]],[[532]],[[533]],[[534]]]},{type:"Polygon",properties:{name:"Rwanda"},id:"RWA",arcs:[[535,-61,-198,536]]},{type:"Polygon",properties:{name:"Western Sahara"},id:"ESH",arcs:[[-241,-458,537,-431]]},{type:"Polygon",properties:{name:"Saudi Arabia"},id:"SAU",arcs:[[538,-382,-365,-410,539,-512,540,-23,-484,541]]},{type:"Polygon",properties:{name:"Sudan"},id:"SDN",arcs:[[542,543,-123,544,-421,-249,545,-254,-268,546]]},{type:"Polygon",properties:{name:"South Sudan"},id:"SSD",arcs:[[547,-266,-396,548,-203,-125,549,-543]]},{type:"Polygon",properties:{name:"Senegal"},id:"SEN",arcs:[[550,-456,-442,-301,-306,551,-304]]},{type:"MultiPolygon",properties:{name:"Solomon Islands"},id:"SLB",arcs:[[[552]],[[553]],[[554]],[[555]],[[556]]]},{type:"Polygon",properties:{name:"Sierra Leone"},id:"SLE",arcs:[[557,-298,-417]]},{type:"Polygon",properties:{name:"El Salvador"},id:"SLV",arcs:[[558,-317,-322]]},{type:"Polygon",properties:{name:"Somaliland"},id:"-99",arcs:[[-263,-231,559,560]]},{type:"Polygon",properties:{name:"Somalia"},id:"SOM",arcs:[[-397,-264,-561,561]]},{type:"Polygon",properties:{name:"Republic of Serbia"},id:"SRB",arcs:[[-86,-439,-407,-446,-90,-325,-334,-516]]},{type:"Polygon",properties:{name:"Suriname"},id:"SUR",arcs:[[562,-285,563,-283,-110,-319]]},{type:"Polygon",properties:{name:"Slovakia"},id:"SVK",arcs:[[-505,564,-331,-54,-222]]},{type:"Polygon",properties:{name:"Slovenia"},id:"SVN",arcs:[[-49,-335,-328,565,-378]]},{type:"Polygon",properties:{name:"Sweden"},id:"SWE",arcs:[[-476,-271,566]]},{type:"Polygon",properties:{name:"Swaziland"},id:"SWZ",arcs:[[567,-451]]},{type:"Polygon",properties:{name:"Syria"},id:"SYR",arcs:[[-381,-375,-415,568,569,-367]]},{type:"Polygon",properties:{name:"Chad"},id:"TCD",arcs:[[-469,-422,-545,-122,-195]]},{type:"Polygon",properties:{name:"Togo"},id:"TGO",arcs:[[570,-296,-76,-69]]},{type:"Polygon",properties:{name:"Thailand"},id:"THA",arcs:[[571,-462,572,-444,-411,-400]]},{type:"Polygon",properties:{name:"Tajikistan"},id:"TJK",arcs:[[-398,-179,-3,573]]},{type:"Polygon",properties:{name:"Turkmenistan"},id:"TKM",arcs:[[-357,574,-389,575,-1]]},{type:"Polygon",properties:{name:"East Timor"},id:"TLS",arcs:[[576,-337]]},{type:"Polygon",properties:{name:"Trinidad and Tobago"},id:"TTO",arcs:[[577]]},{type:"Polygon",properties:{name:"Tunisia"},id:"TUN",arcs:[[-244,578,-419]]},{type:"MultiPolygon",properties:{name:"Turkey"},id:"TUR",arcs:[[[-294,-36,-361,-368,-570,579]],[[-311,-83,580]]]},{type:"Polygon",properties:{name:"Taiwan"},id:"TWN",arcs:[[581]]},{type:"Polygon",properties:{name:"United Republic of Tanzania"},id:"TZA",arcs:[[-394,582,-448,-460,583,-199,-62,-536,584]]},{type:"Polygon",properties:{name:"Uganda"},id:"UGA",arcs:[[-537,-197,-549,-395,-585]]},{type:"Polygon",properties:{name:"Ukraine"},id:"UKR",arcs:[[-530,585,-514,-433,-513,-332,-565,-504,-96]]},{type:"Polygon",properties:{name:"Uruguay"},id:"URY",arcs:[[-113,586,-28]]},{type:"MultiPolygon",properties:{name:"United States of America"},id:"USA",arcs:[[[587]],[[588]],[[589]],[[590]],[[591]],[[592,-438,593,-139]],[[594]],[[595]],[[596]],[[-141,597]]]},{type:"Polygon",properties:{name:"Uzbekistan"},id:"UZB",arcs:[[-576,-388,-399,-574,-2]]},{type:"Polygon",properties:{name:"Venezuela"},id:"VEN",arcs:[[598,-320,-108,-210]]},{type:"Polygon",properties:{name:"Vietnam"},id:"VNM",arcs:[[599,-402,-413,-171]]},{type:"MultiPolygon",properties:{name:"Vanuatu"},id:"VUT",arcs:[[[600]],[[601]]]},{type:"Polygon",properties:{name:"West Bank"},id:"PSE",arcs:[[-384,-371]]},{type:"Polygon",properties:{name:"Yemen"},id:"YEM",arcs:[[602,-542,-483]]},{type:"Polygon",properties:{name:"South Africa"},id:"ZAF",arcs:[[-467,-118,603,-452,-568,-450,604],[-424]]},{type:"Polygon",properties:{name:"Zambia"},id:"ZMB",arcs:[[-459,-454,605,-120,-466,-7,-200,-584]]},{type:"Polygon",properties:{name:"Zimbabwe"},id:"ZWE",arcs:[[-604,-121,-606,-453]]}]}},arcs:[[[6700,7164],[28,-23],[21,8],[6,27],[22,9],[15,18],[6,47],[23,11],[5,21],[13,-15],[8,-2]],[[6847,7265],[16,-1],[20,-12]],[[6883,7252],[9,-7],[20,19],[9,-12],[9,27],[17,-1],[4,9],[3,24],[12,20],[15,-13],[-3,-18],[9,-3],[-3,-50],[11,-19],[10,12],[12,6],[17,27],[19,-5],[29,0]],[[7082,7268],[5,-17]],[[7087,7251],[-16,-6],[-14,-11],[-32,-7],[-30,-13],[-16,-25],[6,-25],[4,-30],[-14,-25],[1,-22],[-8,-22],[-26,2],[11,-39],[-18,-15],[-12,-35],[2,-36],[-11,-16],[-10,5],[-22,-8],[-3,-16],[-20,0],[-16,-34],[-1,-50],[-36,-24],[-19,5],[-6,-13],[-16,7],[-28,-8],[-47,30]],[[6690,6820],[25,53],[-2,38],[-21,10],[-2,38],[-9,47],[12,32],[-12,9],[7,43],[12,74]],[[5664,4412],[3,-18],[-4,-29],[5,-28],[-4,-22],[3,-20],[-58,1],[-2,-188],[19,-49],[18,-37]],[[5644,4022],[-51,-24],[-67,9],[-19,28],[-113,-3],[-4,-4],[-17,27],[-18,2],[-16,-10],[-14,-12]],[[5325,4035],[-2,38],[4,51],[9,55],[2,25],[9,53],[6,24],[16,39],[9,26],[3,44],[-1,34],[-9,21],[-7,36],[-7,35],[2,12],[8,24],[-8,57],[-6,39],[-14,38],[3,11]],[[5342,4697],[11,8],[8,-1],[10,7],[82,-1],[7,-44],[8,-35],[6,-19],[11,-31],[18,5],[9,8],[16,-8],[4,14],[7,35],[17,2],[2,10],[14,1],[-3,-22],[34,1],[1,-37],[5,-23],[-4,-36],[2,-36],[9,-22],[-1,-70],[7,5],[12,-1],[17,8],[13,-3]],[[5338,4715],[-8,45]],[[5330,4760],[12,25],[8,10],[10,-20]],[[5360,4775],[-10,-12],[-4,-16],[-1,-25],[-7,-7]],[[5571,7530],[-3,-20],[4,-25],[11,-15]],[[5583,7470],[0,-15],[-9,-9],[-2,-19],[-13,-29]],[[5559,7398],[-5,5],[0,13],[-15,19],[-3,29],[2,40],[4,18],[-4,10]],[[5538,7532],[-2,18],[12,29],[1,-11],[8,6]],[[5557,7574],[6,-16],[7,-6],[1,-22]],[[6432,6490],[5,3],[1,-16],[22,9],[23,-2],[17,-1],[19,39],[20,38],[18,37]],[[6557,6597],[5,-20]],[[6562,6577],[4,-47]],[[6566,6530],[-14,0],[-3,-39],[5,-8],[-12,-12],[0,-24],[-8,-24],[-1,-24]],[[6533,6399],[-6,-12],[-83,29],[-11,60],[-1,14]],[[3140,1814],[-17,2],[-30,0],[0,132]],[[3093,1948],[11,-27],[14,-45],[36,-35],[39,-15],[-13,-30],[-26,-2],[-14,20]],[[3258,3743],[51,-96],[23,-9],[34,-44],[29,-23],[4,-26],[-28,-90],[28,-16],[32,-9],[22,10],[25,45],[4,52]],[[3482,3537],[14,11],[14,-34],[-1,-47],[-23,-33],[-19,-24],[-31,-57],[-37,-81]],[[3399,3272],[-7,-47],[-7,-61],[0,-58],[-6,-14],[-2,-38]],[[3377,3054],[-2,-31],[35,-50],[-4,-41],[18,-26],[-2,-29],[-26,-75],[-42,-32],[-55,-12],[-31,6],[6,-36],[-6,-44],[5,-30],[-16,-20],[-29,-8],[-26,21],[-11,-15],[4,-59],[18,-18],[16,19],[8,-31],[-26,-18],[-22,-37],[-4,-59],[-7,-32],[-26,0],[-22,-31],[-8,-44],[28,-43],[26,-12],[-9,-53],[-33,-33],[-18,-70],[-25,-23],[-12,-28],[9,-61],[19,-34],[-12,3]],[[3095,1968],[-26,9],[-67,8],[-11,34],[0,45],[-18,-4],[-10,21],[-3,63],[22,26],[9,37],[-4,30],[15,51],[10,78],[-3,35],[12,11],[-3,22],[-13,12],[10,25],[-13,22],[-6,68],[11,12],[-5,72],[7,61],[7,52],[17,22],[-9,58],[0,54],[21,38],[-1,50],[16,57],[0,55],[-7,11],[-13,102],[17,60],[-2,58],[10,53],[18,56],[20,36],[-9,24],[6,19],[-1,98],[30,29],[10,62],[-3,14]],[[3136,3714],[23,54],[36,-15],[16,-42],[11,47],[32,-2],[4,-13]],[[6210,7485],[39,9]],[[6249,7494],[5,-15],[11,-10],[-6,-15],[15,-21],[-8,-18],[12,-16],[13,-10],[0,-41]],[[6291,7348],[-10,-2]],[[6281,7346],[-11,34],[0,10],[-12,-1],[-9,16],[-5,-1]],[[6244,7404],[-11,17],[-21,15],[3,28],[-5,21]],[[3345,329],[-8,-30],[-8,-27],[-59,8],[-62,-3],[-34,20],[0,2],[-16,17],[63,-2],[60,-6],[20,24],[15,21],[29,-24]],[[577,361],[-53,-8],[-36,21],[-17,21],[-1,3],[-18,16],[17,22],[52,-9],[28,-18],[21,-21],[7,-27]],[[3745,447],[35,-26],[12,-36],[3,-25],[1,-30],[-43,-19],[-45,-15],[-52,-14],[-59,-11],[-65,3],[-37,20],[5,24],[59,16],[24,20],[18,26],[12,22],[17,20],[18,25],[14,0],[41,12],[42,-12]],[[1633,715],[36,-9],[33,10],[-16,-20],[-26,-15],[-39,4],[-27,21],[6,20],[33,-11]],[[1512,716],[43,-23],[-17,3],[-36,5],[-38,17],[20,12],[28,-14]],[[2250,808],[31,-8],[30,7],[17,-34],[-22,5],[-34,-2],[-34,2],[-38,-4],[-28,12],[-15,24],[18,11],[35,-8],[40,-5]],[[3098,866],[4,-27],[-5,-23],[-8,-22],[-33,-8],[-31,-12],[-36,1],[14,24],[-33,-9],[-31,-8],[-21,18],[-2,24],[30,23],[20,7],[32,-2],[8,30],[1,22],[0,47],[16,28],[25,9],[15,-22],[6,-22],[12,-26],[10,-26],[7,-26]],[[3371,1268],[-11,-13],[-21,9],[-23,-6],[-19,-14],[-20,-15],[-14,-17],[-4,-23],[2,-22],[13,-20],[-19,-14],[-26,-4],[-15,-20],[-17,-19],[-17,-25],[-4,-22],[9,-24],[15,-19],[23,-14],[21,-18],[12,-23],[6,-22],[8,-24],[13,-19],[8,-22],[4,-55],[8,-22],[2,-23],[9,-23],[-4,-31],[-15,-24],[-17,-20],[-37,-8],[-12,-21],[-17,-20],[-42,-22],[-37,-9],[-35,-13],[-37,-13],[-22,-24],[-45,-2],[-49,2],[-44,-4],[-47,0],[9,-24],[42,-10],[31,-16],[18,-21],[-31,-19],[-48,6],[-40,-15],[-2,-24],[-1,-23],[33,-20],[6,-22],[35,-22],[59,-9],[50,-16],[40,-19],[50,-18],[70,-10],[68,-16],[47,-17],[52,-20],[27,-28],[13,-22],[34,21],[46,17],[48,19],[58,15],[49,16],[69,1],[68,-8],[56,-14],[18,26],[39,17],[70,1],[55,13],[52,13],[58,8],[62,10],[43,15],[-20,21],[-12,21],[0,22],[-54,-2],[-57,-10],[-54,0],[-8,22],[4,44],[12,13],[40,14],[47,14],[34,17],[33,18],[25,23],[38,10],[38,8],[19,5],[43,2],[41,8],[34,12],[34,14],[30,14],[39,18],[24,20],[26,17],[9,24],[-30,13],[10,25],[18,18],[29,12],[31,14],[28,18],[22,23],[13,28],[21,16],[33,-3],[13,-20],[34,-2],[1,22],[14,23],[30,-6],[7,-22],[33,-3],[36,10],[35,7],[31,-3],[12,-25],[31,20],[28,10],[31,9],[31,8],[29,14],[31,9],[24,13],[17,20],[20,-15],[29,8],[20,-27],[16,-21],[32,11],[12,24],[28,16],[37,-4],[11,-22],[22,22],[30,7],[33,3],[29,-2],[31,-7],[30,-3],[13,-20],[18,-17],[31,10],[32,3],[32,0],[31,1],[28,8],[29,7],[25,16],[26,11],[28,5],[21,17],[15,32],[16,20],[29,-10],[11,-21],[24,-13],[29,4],[19,-21],[21,-15],[28,14],[10,26],[25,10],[29,20],[27,8],[33,11],[22,13],[22,14],[22,13],[26,-7],[25,21],[18,16],[26,-1],[23,14],[6,21],[23,16],[23,11],[28,10],[25,4],[25,-3],[26,-6],[22,-16],[3,-26],[24,-19],[17,-17],[33,-7],[19,-16],[23,-16],[26,-3],[23,11],[24,24],[26,-12],[27,-7],[26,-7],[27,-5],[28,0],[23,-61],[-1,-15],[-4,-27],[-26,-15],[-22,-22],[4,-23],[31,1],[-4,-23],[-14,-22],[-13,-24],[21,-19],[32,-6],[32,11],[15,23],[10,22],[15,18],[17,18],[7,21],[15,29],[18,5],[31,3],[28,7],[28,9],[14,23],[8,22],[19,22],[27,15],[23,12],[16,19],[15,11],[21,9],[27,-6],[25,6],[28,7],[30,-4],[20,17],[14,39],[11,-16],[13,-28],[23,-12],[27,-4],[26,7],[29,-5],[26,-1],[17,6],[24,-4],[21,-12],[25,8],[30,0],[25,8],[29,-8],[19,19],[14,20],[19,16],[35,44],[18,-8],[21,-16],[18,-21],[36,-36],[27,-1],[25,0],[30,7],[30,8],[23,16],[19,18],[31,2],[21,13],[22,-12],[14,-18],[19,-19],[31,2],[19,-15],[33,-15],[35,-5],[29,4],[21,19],[19,18],[25,5],[25,-8],[29,-6],[26,9],[25,0],[24,-6],[26,-5],[25,10],[30,9],[28,3],[32,0],[25,5],[25,5],[8,29],[1,24],[17,-16],[5,-27],[10,-24],[11,-20],[23,-10],[32,4],[36,1],[25,3],[37,0],[26,1],[36,-2],[31,-5],[20,-18],[-5,-22],[18,-18],[30,-13],[31,-15],[35,-11],[38,-9],[28,-9],[32,-2],[18,20],[24,-16],[21,-19],[25,-13],[34,-6],[32,-7],[13,-23],[32,-14],[21,-21],[31,-9],[32,1],[30,-4],[33,1],[34,-4],[31,-8],[28,-14],[29,-12],[20,-17],[-3,-23],[-15,-21],[-13,-27],[-9,-21],[-14,-24],[-36,-9],[-16,-21],[-36,-13],[-13,-23],[-19,-22],[-20,-18],[-11,-25],[-7,-22],[-3,-26],[0,-22],[16,-23],[6,-22],[13,-21],[52,-8],[11,-26],[-50,-9],[-43,-13],[-52,-2],[-24,-34],[-5,-27],[-12,-22],[-14,-22],[37,-20],[14,-24],[24,-22],[33,-20],[39,-19],[42,-18],[64,-19],[14,-29],[80,-12],[5,-5],[21,-17],[77,15],[63,-19],[48,-14],[-9997,-1],[24,35],[50,-19],[3,2],[30,19],[4,0],[3,-1],[40,-25],[35,25],[7,3],[81,11],[27,-14],[13,-7],[41,-20],[79,-15],[63,-18],[107,-14],[80,16],[118,-11],[67,-19],[73,17],[78,17],[6,27],[-110,3],[-89,14],[-24,23],[-74,12],[5,27],[10,24],[10,22],[-5,25],[-46,16],[-22,21],[-43,18],[68,-3],[64,9],[40,-20],[50,18],[45,22],[23,19],[-10,25],[-36,16],[-41,17],[-57,4],[-50,8],[-54,6],[-18,22],[-36,18],[-21,21],[-9,67],[14,-6],[25,-18],[45,6],[44,8],[23,-26],[44,6],[37,13],[35,16],[32,20],[41,5],[-1,22],[-9,22],[8,21],[36,11],[16,-20],[42,12],[32,15],[40,1],[38,6],[37,13],[30,13],[34,13],[22,-4],[19,-4],[41,8],[37,-10],[38,1],[37,8],[37,-6],[41,-6],[39,3],[40,-2],[42,-1],[38,3],[28,17],[34,9],[35,-13],[33,11],[30,21],[18,-19],[9,-21],[18,-19],[29,17],[33,-22],[38,-7],[32,-16],[39,3],[36,11],[41,-3],[38,-8],[38,-10],[15,25],[-18,20],[-14,21],[-36,5],[-15,22],[-6,22],[-10,43],[21,-8],[36,-3],[36,3],[33,-9],[28,-17],[12,-21],[38,-4],[36,9],[38,11],[34,7],[28,-14],[37,5],[24,45],[23,-27],[32,-10],[34,6],[23,-23],[37,-3],[33,-7],[34,-12],[21,22],[11,20],[28,-23],[38,6],[28,-13],[19,-19],[37,5],[29,13],[29,15],[33,8],[39,7],[36,8],[27,13],[16,19],[7,25],[-3,24],[-9,24],[-10,23],[-9,23],[-7,21],[-1,23],[2,23],[13,22],[11,24],[5,23],[-6,26],[-3,23],[14,27],[15,17],[18,22],[19,19],[22,17],[11,25],[15,17],[18,15],[26,3],[18,19],[19,11],[23,7],[20,15],[16,19],[22,7],[16,-15],[-10,-20],[-29,-17]],[[6914,2185],[18,-19],[26,-7],[1,-11],[-7,-27],[-43,-4],[-1,31],[4,25],[2,12]],[[9038,2648],[27,-21],[15,8],[22,12],[16,-4],[2,-70],[-9,-21],[-3,-47],[-10,16],[-19,-41],[-6,3],[-17,2],[-17,50],[-4,39],[-16,52],[1,27],[18,-5]],[[8987,4244],[10,-46],[18,22],[9,-25],[13,-23],[-3,-26],[6,-51],[5,-29],[7,-7],[7,-51],[-3,-30],[9,-40],[31,-31],[19,-28],[19,-26],[-4,-14],[16,-37],[11,-64],[11,13],[11,-26],[7,9],[5,-63],[19,-36],[13,-22],[22,-48],[8,-48],[1,-33],[-2,-37],[13,-50],[-2,-52],[-5,-28],[-7,-52],[1,-34],[-6,-43],[-12,-53],[-21,-29],[-10,-46],[-9,-29],[-8,-51],[-11,-30],[-7,-44],[-4,-41],[2,-18],[-16,-21],[-31,-2],[-26,-24],[-13,-23],[-17,-26],[-23,27],[-17,10],[5,31],[-15,-11],[-25,-43],[-24,16],[-15,9],[-16,4],[-27,17],[-18,37],[-5,45],[-7,30],[-13,24],[-27,7],[9,28],[-7,44],[-13,-41],[-25,-11],[14,33],[5,34],[10,29],[-2,44],[-22,-50],[-18,-21],[-10,-47],[-22,25],[1,31],[-18,43],[-14,22],[5,14],[-36,35],[-19,2],[-27,29],[-50,-6],[-36,-21],[-31,-20],[-27,4],[-29,-30],[-24,-14],[-6,-31],[-10,-24],[-23,-1],[-18,-5],[-24,10],[-20,-6],[-19,-3],[-17,-31],[-8,2],[-14,-16],[-13,-19],[-21,2],[-18,0],[-30,38],[-15,11],[1,34],[14,8],[4,14],[-1,21],[4,41],[-3,35],[-15,60],[-4,33],[1,34],[-11,38],[-1,18],[-12,23],[-4,47],[-16,46],[-4,26],[13,-26],[-10,55],[14,-17],[8,-23],[0,30],[-14,47],[-3,18],[-6,18],[3,34],[6,15],[4,29],[-3,35],[11,42],[2,-45],[12,41],[22,20],[14,25],[21,22],[13,4],[7,-7],[22,22],[17,6],[4,13],[8,6],[15,-2],[29,18],[15,26],[7,31],[17,30],[1,24],[1,32],[19,50],[12,-51],[12,12],[-10,28],[9,29],[12,-13],[3,45],[15,29],[7,23],[14,10],[0,17],[13,-7],[0,15],[12,8],[14,8],[20,-27],[16,-35],[17,0],[18,-6],[-6,33],[13,47],[13,15],[-5,15],[12,34],[17,21],[14,-7],[24,11],[-1,30],[-20,19],[15,9],[18,-15],[15,-24],[23,-15],[8,6],[17,-18],[17,17],[10,-5],[7,11],[12,-29],[-7,-32],[-11,-24],[-9,-2],[3,-23],[-8,-30],[-10,-29],[2,-17],[22,-32],[21,-19],[15,-20],[20,-35],[8,0],[14,-15],[4,-19],[27,-20],[18,20],[6,32],[5,26],[4,33],[8,47],[-4,28],[2,17],[-3,34],[4,45],[5,12],[-4,20],[7,31],[5,32],[1,17],[10,22],[8,-29],[2,-37],[7,-7],[1,-25],[10,-30],[2,-33],[-1,-22]],[[5471,7900],[-2,-24],[-16,0],[6,-13],[-9,-38]],[[5450,7825],[-6,-10],[-24,-1],[-14,-13],[-23,4]],[[5383,7805],[-40,15],[-6,21],[-27,-10],[-4,-12],[-16,9]],[[5290,7828],[-15,1],[-12,11],[4,15],[-1,10]],[[5266,7865],[8,3],[14,-16],[4,16],[25,-3],[20,11],[13,-2],[9,-12],[2,10],[-4,38],[10,8],[10,27]],[[5377,7945],[21,-19],[15,24],[10,5],[22,-18],[13,3],[13,-12]],[[5471,7928],[-3,-7],[3,-21]],[[6281,7346],[-19,8],[-14,27],[-4,23]],[[6349,7527],[15,-31],[14,-42],[13,-2],[8,-16],[-23,-5],[-5,-46],[-4,-21],[-11,-13],[1,-30]],[[6357,7321],[-7,-3],[-17,31],[10,30],[-9,17],[-10,-4],[-33,-44]],[[6249,7494],[6,10],[21,-17],[15,-4],[4,7],[-14,32],[7,9]],[[6288,7531],[8,-2],[19,-36],[13,-4],[4,15],[17,23]],[[5814,4792],[-1,71],[-7,27]],[[5806,4890],[17,-5],[8,34],[15,-4]],[[5846,4915],[1,-23],[6,-14],[1,-19],[-7,-12],[-11,-31],[-10,-22],[-12,-2]],[[5092,8091],[20,-5],[26,12],[17,-25],[16,-14]],[[5171,8059],[-4,-40]],[[5167,8019],[-7,-2],[-3,-33]],[[5157,7984],[-24,26],[-14,-4],[-20,28],[-13,23],[-13,1],[-4,21]],[[5069,8079],[23,12]],[[5074,5427],[-23,-7]],[[5051,5420],[-7,41],[2,136],[-6,12],[-1,29],[-10,21],[-8,17],[3,31]],[[5024,5707],[10,7],[6,26],[13,5],[6,18]],[[5059,5763],[10,17],[10,0],[21,-34]],[[5100,5746],[-1,-19],[6,-35],[-6,-24],[3,-16],[-13,-37],[-9,-18],[-5,-37],[1,-38],[-2,-95]],[[4921,5627],[-19,15],[-13,-2],[-10,-15],[-12,13],[-5,19],[-13,13]],[[4849,5670],[-1,34],[7,26],[-1,20],[23,48],[4,41],[7,14],[14,-8],[11,12],[4,16],[22,26],[5,19],[26,24],[15,9],[7,-12],[18,0]],[[5010,5939],[-2,-28],[3,-27],[16,-39],[1,-28],[32,-14],[-1,-40]],[[5024,5707],[-24,1]],[[5e3,5708],[-13,5],[-9,-9],[-12,4],[-48,-3],[-1,-33],[4,-45]],[[7573,6360],[0,-43],[-10,9],[2,-47]],[[7565,6279],[-8,30],[-1,31],[-6,28],[-11,34],[-26,3],[3,-25],[-9,-32],[-12,12],[-4,-11],[-8,6],[-11,5]],[[7472,6360],[-4,49],[-10,45],[5,35],[-17,16],[6,22],[18,22],[-20,31],[9,40],[22,-26],[14,-3],[2,-41],[26,-8],[26,1],[16,-10],[-13,-50],[-12,-3],[-9,-34],[16,-31],[4,38],[8,0],[14,-93]],[[5629,7671],[8,-25],[11,5],[21,-9],[41,-4],[13,16],[33,13],[20,-21],[17,-6]],[[5793,7640],[-15,-25],[-10,-42],[9,-34]],[[5777,7539],[-24,8],[-28,-18]],[[5725,7529],[0,-30],[-26,-5],[-19,20],[-22,-16],[-21,2]],[[5637,7500],[-2,39],[-14,19]],[[5621,7558],[5,8],[-3,7],[4,19],[11,18],[-14,26],[-2,21],[7,14]],[[2846,6461],[-7,-3],[-7,34],[-10,17],[6,38],[8,-3],[10,-49],[0,-34]],[[2838,6628],[-30,-10],[-2,22],[13,5],[18,-2],[1,-15]],[[2861,6628],[-5,-42],[-5,8],[0,31],[-12,23],[0,7],[22,-27]],[[5527,7708],[10,0],[-7,-26],[14,-23],[-4,-28],[-7,-2]],[[5533,7629],[-5,-6],[-9,-13],[-4,-33]],[[5515,7577],[-25,23],[-10,24],[-11,13],[-12,22],[-6,19],[-14,27],[6,25],[10,-14],[6,12],[13,2],[24,-10],[19,1],[12,-13]],[[5652,8242],[27,0],[30,22],[6,34],[23,19],[-3,26]],[[5735,8343],[17,10],[30,23]],[[5782,8376],[29,-15],[4,-15],[15,7],[27,-14],[3,-27],[-6,-16],[17,-39],[12,-11],[-2,-11],[19,-10],[8,-16],[-11,-13],[-23,2],[-5,-5],[7,-20],[6,-37]],[[5882,8136],[-23,-4],[-9,-13],[-2,-30],[-11,6],[-25,-3],[-7,14],[-11,-10],[-10,8],[-22,1],[-31,15],[-28,4],[-22,-1],[-15,-16],[-13,-2]],[[5653,8105],[-1,26],[-8,27],[17,12],[0,24],[-8,22],[-1,26]],[[2524,6110],[-1,8],[4,3],[5,-7],[10,36],[5,0]],[[2547,6150],[0,-8],[5,-1],[0,-16],[-5,-25],[3,-9],[-3,-21],[2,-6],[-4,-30],[-5,-16],[-5,-1],[-6,-21]],[[2529,5996],[-8,0],[2,67],[1,47]],[[3136,3714],[-20,-8],[-11,82],[-15,66],[9,57],[-15,25],[-4,43],[-13,40]],[[3067,4019],[17,64],[-12,49],[7,20],[-5,22],[10,30],[1,50],[1,41],[6,20],[-24,96]],[[3068,4411],[21,-5],[14,1],[6,18],[25,24],[14,22],[37,10],[-3,-44],[3,-23],[-2,-40],[30,-53],[31,-9],[11,-23],[19,-11],[11,-17],[18,0],[16,-17],[1,-34],[6,-18],[0,-25],[-8,-1],[11,-69],[53,-2],[-4,-35],[3,-23],[15,-16],[6,-37],[-4,-47],[-8,-26],[3,-33],[-9,-12]],[[3384,3866],[-1,18],[-25,30],[-26,1],[-49,-17],[-13,-52],[-1,-32],[-11,-71]],[[3482,3537],[6,34],[3,35],[1,32],[-10,11],[-11,-9],[-10,2],[-4,23],[-2,54],[-5,18],[-19,16],[-11,-12],[-30,11],[2,81],[-8,33]],[[3068,4411],[-15,-11],[-13,7],[2,90],[-23,-35],[-24,2],[-11,31],[-18,4],[5,25],[-15,36],[-11,53],[7,11],[0,25],[17,17],[-3,32],[7,20],[2,28],[32,40],[22,11],[4,9],[25,-2]],[[3058,4804],[13,162],[0,25],[-4,34],[-12,22],[0,42],[15,10],[6,-6],[1,23],[-16,6],[-1,37],[54,-2],[10,21],[7,-19],[6,-35],[5,8]],[[3142,5132],[15,-32],[22,4],[5,18],[21,14],[11,10],[4,25],[19,17],[-1,12],[-24,5],[-3,37],[1,40],[-13,15],[5,6],[21,-8],[22,-15],[8,14],[20,9],[31,23],[10,22],[-3,17]],[[3313,5365],[14,2],[7,-13],[-4,-26],[9,-9],[7,-28],[-8,-20],[-4,-51],[7,-30],[2,-27],[17,-28],[14,-3],[3,12],[8,3],[13,10],[9,16],[15,-5],[7,2]],[[3429,5170],[15,-5],[3,12],[-5,12],[3,17],[11,-5],[13,6],[16,-13]],[[3485,5194],[12,-12],[9,16],[6,-3],[4,-16],[13,4],[11,22],[8,44],[17,54]],[[3565,5303],[9,3],[7,-33],[16,-103],[14,-10],[1,-41],[-21,-48],[9,-18],[49,-9],[1,-60],[21,39],[35,-21],[46,-36],[14,-35],[-5,-32],[33,18],[54,-32],[41,3],[41,-49],[36,-66],[21,-17],[24,-3],[10,-18],[9,-76],[5,-35],[-11,-98],[-14,-39],[-39,-82],[-18,-67],[-21,-51],[-7,-1],[-7,-43],[2,-111],[-8,-91],[-3,-39],[-9,-23],[-5,-79],[-28,-77],[-5,-61],[-22,-26],[-7,-35],[-30,0],[-44,-23],[-19,-26],[-31,-18],[-33,-47],[-23,-58],[-5,-44],[5,-33],[-5,-60],[-6,-28],[-20,-33],[-31,-104],[-24,-47],[-19,-27],[-13,-57],[-18,-33]],[[3517,3063],[-8,33],[13,28],[-16,40],[-22,33],[-29,38],[-10,-2],[-28,46],[-18,-7]],[[8172,5325],[11,22],[23,32]],[[8206,5379],[-1,-29],[-2,-37],[-13,1],[-6,-20],[-12,31]],[[7546,6698],[12,-19],[-2,-36],[-23,-2],[-23,4],[-18,-9],[-25,22],[-1,12]],[[7466,6670],[19,44],[15,15],[20,-14],[14,-1],[12,-16]],[[5817,3752],[-39,-43],[-25,-44],[-10,-40],[-8,-22],[-15,-4],[-5,-29],[-3,-18],[-17,-14],[-23,3],[-13,17],[-12,7],[-14,-14],[-6,-28],[-14,-18],[-13,-26],[-20,-6],[-6,20],[2,36],[-16,56],[-8,9]],[[5552,3594],[0,173],[27,2],[1,210],[21,2],[43,21],[10,-24],[18,23],[9,0],[15,13]],[[5696,4014],[5,-4]],[[5701,4010],[11,-48],[5,-10],[9,-34],[32,-65],[12,-7],[0,-20],[8,-38],[21,-9],[18,-27]],[[5424,5496],[23,4],[5,16],[5,-2],[7,-13],[34,23],[12,23],[15,20],[-3,21],[8,6],[27,-4],[26,27],[20,65],[14,24],[18,10]],[[5635,5716],[3,-26],[16,-36],[0,-25],[-5,-24],[2,-18],[10,-18]],[[5661,5569],[21,-25]],[[5682,5544],[15,-24],[0,-19],[19,-31],[12,-26],[7,-35],[20,-24],[5,-18]],[[5760,5367],[-9,-7],[-18,2],[-21,6],[-10,-5],[-5,-14],[-9,-2],[-10,12],[-31,-29],[-13,6],[-4,-5],[-8,-35],[-21,11],[-20,6],[-18,22],[-23,20],[-15,-19],[-10,-30],[-3,-41]],[[5512,5265],[-18,3],[-19,10],[-16,-32],[-15,-55]],[[5444,5191],[-3,18],[-1,27],[-13,19],[-10,30],[-2,21],[-13,31],[2,18],[-3,25],[2,45],[7,11],[14,60]],[[3231,7808],[20,-8],[26,1],[-14,-24],[-10,-4],[-35,25],[-7,20],[10,18],[10,-28]],[[3283,7958],[-14,-1],[-36,19],[-26,28],[10,5],[37,-15],[28,-25],[1,-11]],[[1569,7923],[-14,-8],[-46,27],[-8,21],[-25,21],[-5,16],[-28,11],[-11,32],[2,14],[30,-13],[17,-9],[26,-6],[9,-21],[14,-28],[28,-24],[11,-33]],[[3440,8052],[-18,-52],[18,20],[19,-12],[-10,-21],[25,-16],[12,14],[28,-18],[-8,-43],[19,10],[4,-32],[8,-36],[-11,-52],[-13,-2],[-18,11],[6,48],[-8,8],[-32,-52],[-17,2],[20,28],[-27,14],[-30,-3],[-54,2],[-4,17],[17,21],[-12,16],[24,36],[28,94],[18,33],[24,21],[13,-3],[-6,-16],[-15,-37]],[[1313,8250],[27,5],[-8,-67],[24,-48],[-11,0],[-17,27],[-10,27],[-14,19],[-5,26],[1,19],[13,-8]],[[2798,8730],[-11,-31],[-12,5],[-8,17],[2,4],[10,18],[12,-1],[7,-12]],[[2725,8762],[-33,-32],[-19,1],[-6,16],[20,27],[38,0],[0,-12]],[[2634,8936],[5,-26],[15,9],[16,-15],[30,-20],[32,-19],[2,-28],[21,5],[20,-20],[-25,-18],[-43,14],[-16,26],[-27,-31],[-40,-31],[-9,35],[-38,-6],[24,30],[4,46],[9,54],[20,-5]],[[2892,9024],[-31,-3],[-7,29],[12,34],[26,8],[21,-17],[1,-25],[-4,-8],[-18,-18]],[[2343,9140],[-17,-21],[-38,18],[-22,-6],[-38,26],[24,19],[19,25],[30,-16],[17,-11],[8,-11],[17,-23]],[[3135,7724],[-18,33],[0,81],[-13,17],[-18,-10],[-10,16],[-21,-45],[-8,-46],[-10,-27],[-12,-9],[-9,-3],[-3,-15],[-51,0],[-42,0],[-12,-11],[-30,-42],[-3,-5],[-9,-23],[-26,0],[-27,0],[-12,-10],[4,-11],[2,-18],[0,-6],[-36,-30],[-29,-9],[-32,-31],[-7,0],[-10,9],[-3,8],[1,6],[6,21],[13,33],[8,35],[-5,51],[-6,53],[-29,28],[3,11],[-4,7],[-8,0],[-5,9],[-2,14],[-5,-6],[-7,2],[1,6],[-6,6],[-3,15],[-21,19],[-23,20],[-27,23],[-26,21],[-25,-17],[-9,0],[-34,15],[-23,-8],[-27,19],[-28,9],[-19,4],[-9,10],[-5,32],[-9,0],[-1,-23],[-57,0],[-95,0],[-94,0],[-84,0],[-83,0],[-82,0],[-85,0],[-27,0],[-82,0],[-79,0]],[[1588,7952],[-4,0],[-54,58],[-20,26],[-50,24],[-15,53],[3,36],[-35,25],[-5,48],[-34,43],[0,30]],[[1374,8295],[15,29],[0,37],[-48,37],[-28,68],[-17,42],[-26,27],[-19,24],[-14,31],[-28,-20],[-27,-33],[-25,39],[-19,26],[-27,16],[-28,2],[0,337],[1,219]],[[1084,9176],[51,-14],[44,-29],[29,-5],[24,24],[34,19],[41,-7],[42,26],[45,14],[20,-24],[20,14],[6,27],[20,-6],[47,-53],[37,40],[3,-45],[34,10],[11,17],[34,-3],[42,-25],[65,-22],[38,-10],[28,4],[37,-30],[-39,-29],[50,-13],[75,7],[24,11],[29,-36],[31,30],[-29,25],[18,20],[34,3],[22,6],[23,-14],[28,-32],[31,5],[49,-27],[43,9],[40,-1],[-3,37],[25,10],[43,-20],[0,-56],[17,47],[23,-1],[12,59],[-30,36],[-32,24],[2,65],[33,43],[37,-9],[28,-26],[38,-67],[-25,-29],[52,-12],[-1,-60],[38,46],[33,-38],[-9,-44],[27,-40],[29,43],[21,51],[1,65],[40,-5],[41,-8],[37,-30],[2,-29],[-21,-31],[20,-32],[-4,-29],[-54,-41],[-39,-9],[-29,18],[-8,-30],[-27,-50],[-8,-26],[-32,-40],[-40,-4],[-22,-25],[-2,-38],[-32,-7],[-34,-48],[-30,-67],[-11,-46],[-1,-69],[40,-10],[13,-55],[13,-45],[39,12],[51,-26],[28,-22],[20,-28],[35,-17],[29,-24],[46,-4],[30,-6],[-4,-51],[8,-59],[21,-66],[41,-56],[21,19],[15,61],[-14,93],[-20,31],[45,28],[31,41],[16,41],[-3,40],[-19,50],[-33,44],[32,62],[-12,54],[-9,92],[19,14],[48,-16],[29,-6],[23,15],[25,-20],[35,-34],[8,-23],[50,-4],[-1,-50],[9,-74],[25,-10],[21,-35],[40,33],[26,65],[19,28],[21,-53],[36,-75],[31,-71],[-11,-37],[37,-33],[25,-34],[44,-15],[18,-19],[11,-50],[22,-8],[11,-22],[2,-67],[-20,-22],[-20,-21],[-46,-21],[-35,-48],[-47,-10],[-59,13],[-42,0],[-29,-4],[-23,-43],[-35,-26],[-40,-78],[-32,-54],[23,9],[45,78],[58,49],[42,6],[24,-29],[-26,-40],[9,-63],[9,-45],[36,-29],[46,8],[28,67],[2,-43],[17,-22],[-34,-38],[-61,-36],[-28,-23],[-31,-43],[-21,4],[-1,50],[48,49],[-44,-2],[-31,-7]],[[1829,9377],[-14,-27],[61,17],[39,-29],[31,30],[26,-20],[23,-58],[14,25],[-20,60],[24,9],[28,-9],[31,-24],[17,-58],[9,-41],[47,-30],[50,-28],[-3,-26],[-46,-4],[18,-23],[-9,-22],[-51,9],[-48,16],[-32,-3],[-52,-20],[-70,-9],[-50,-6],[-15,28],[-38,16],[-24,-6],[-35,47],[19,6],[43,10],[39,-3],[36,11],[-54,13],[-59,-4],[-39,1],[-15,22],[64,23],[-42,-1],[-49,16],[23,44],[20,24],[74,36],[29,-12]],[[2097,9395],[-24,-39],[-44,41],[10,9],[37,2],[21,-13]],[[2879,9376],[3,-16],[-30,2],[-30,1],[-30,-8],[-8,3],[-31,32],[1,21],[14,4],[63,-6],[48,-33]],[[2595,9379],[22,-36],[26,47],[70,24],[48,-61],[-4,-38],[55,17],[26,23],[62,-30],[38,-28],[3,-25],[52,13],[29,-38],[67,-23],[24,-24],[26,-55],[-51,-28],[66,-38],[44,-13],[40,-55],[44,-3],[-9,-42],[-49,-69],[-34,26],[-44,57],[-36,-8],[-3,-34],[29,-34],[38,-27],[11,-16],[18,-58],[-9,-43],[-35,16],[-70,47],[39,-51],[29,-35],[5,-21],[-76,24],[-59,34],[-34,29],[10,17],[-42,30],[-40,29],[0,-18],[-80,-9],[-23,20],[18,44],[52,1],[57,7],[-9,21],[10,30],[36,57],[-8,27],[-11,20],[-42,29],[-57,20],[18,15],[-29,36],[-25,4],[-22,20],[-14,-18],[-51,-7],[-101,13],[-59,17],[-45,9],[-23,21],[29,27],[-39,0],[-9,60],[21,53],[29,24],[72,16],[-21,-39]],[[2212,9420],[33,-12],[50,7],[7,-17],[-26,-28],[42,-26],[-5,-53],[-45,-23],[-27,5],[-19,23],[-69,45],[0,19],[57,-7],[-31,38],[33,29]],[[2411,9357],[-30,-45],[-32,3],[-17,52],[1,29],[14,25],[28,16],[58,-2],[53,-14],[-42,-53],[-33,-11]],[[1654,9275],[-73,-29],[-15,26],[-64,31],[12,25],[19,43],[24,39],[-27,36],[94,10],[39,-13],[71,-3],[27,-17],[30,-25],[-35,-15],[-68,-41],[-34,-42],[0,-25]],[[2399,9487],[-15,-23],[-40,5],[-34,15],[15,27],[40,16],[24,-21],[10,-19]],[[2264,9590],[21,-27],[1,-31],[-13,-44],[-46,-6],[-30,10],[1,34],[-45,-4],[-2,45],[30,-2],[41,21],[40,-4],[2,8]],[[1994,9559],[11,-21],[25,10],[29,-2],[5,-29],[-17,-28],[-94,-10],[-70,-25],[-43,-2],[-3,20],[57,26],[-125,-7],[-39,10],[38,58],[26,17],[78,-20],[50,-35],[48,-5],[-40,57],[26,21],[29,-7],[9,-28]],[[2370,9612],[30,-19],[55,0],[24,-19],[-6,-22],[32,-14],[17,-14],[38,-2],[40,-5],[44,13],[57,5],[45,-5],[30,-22],[6,-24],[-17,-16],[-42,-13],[-35,8],[-80,-10],[-57,-1],[-45,8],[-74,19],[-9,32],[-4,29],[-27,26],[-58,7],[-32,19],[10,24],[58,-4]],[[1772,9645],[-4,-46],[-21,-20],[-26,-3],[-52,-26],[-44,-9],[-38,13],[47,44],[57,39],[43,-1],[38,9]],[[2393,9637],[-13,-2],[-52,4],[-7,17],[56,-1],[19,-11],[-3,-7]],[[1939,9648],[-52,-17],[-41,19],[23,19],[40,6],[39,-10],[-9,-17]],[[1954,9701],[-34,-11],[-46,0],[0,8],[29,18],[14,-3],[37,-12]],[[2338,9669],[-41,-12],[-23,13],[-12,23],[-2,24],[36,-2],[16,-4],[33,-21],[-7,-21]],[[2220,9685],[11,-25],[-45,7],[-46,19],[-62,2],[27,18],[-34,14],[-2,22],[55,-8],[75,-21],[21,-28]],[[2583,9764],[33,-20],[-38,-17],[-51,-45],[-50,-4],[-57,8],[-30,24],[0,21],[22,16],[-50,0],[-31,19],[-18,27],[20,26],[19,18],[28,4],[-12,14],[65,3],[35,-32],[47,-12],[46,-11],[22,-39]],[[3097,9967],[74,-4],[60,-8],[51,-16],[-2,-16],[-67,-25],[-68,-12],[-25,-14],[61,1],[-66,-36],[-45,-17],[-48,-48],[-57,-10],[-18,-12],[-84,-6],[39,-8],[-20,-10],[23,-29],[-26,-21],[-43,-16],[-13,-24],[-39,-17],[4,-14],[48,3],[0,-15],[-74,-35],[-73,16],[-81,-9],[-42,7],[-52,3],[-4,29],[52,13],[-14,43],[17,4],[74,-26],[-38,38],[-45,11],[23,23],[49,14],[8,21],[-39,23],[-12,31],[76,-3],[22,-6],[43,21],[-62,7],[-98,-4],[-49,20],[-23,24],[-32,17],[-6,21],[41,11],[32,2],[55,9],[41,22],[34,-3],[30,-16],[21,32],[37,9],[50,7],[85,2],[14,-6],[81,10],[60,-4],[60,-4]],[[5290,7828],[-3,-24],[-12,-10],[-20,7],[-6,-24],[-14,-2],[-5,10],[-15,-20],[-13,-3],[-12,13]],[[5190,7775],[-10,25],[-13,-9],[0,27],[21,33],[-1,15],[12,-5],[8,10]],[[5207,7871],[24,-1],[5,13],[30,-18]],[[3140,1814],[-10,-24],[-23,-18],[-14,2],[-16,5],[-21,18],[-29,8],[-35,33],[-28,32],[-38,66],[23,-12],[39,-40],[36,-21],[15,27],[9,41],[25,24],[20,-7]],[[3095,1968],[-25,0],[-13,-14],[-25,-22],[-5,-55],[-11,-1],[-32,19],[-32,41],[-34,34],[-9,37],[8,35],[-14,39],[-4,101],[12,57],[30,45],[-43,18],[27,52],[9,98],[31,-21],[15,123],[-19,15],[-9,-73],[-17,8],[9,84],[9,110],[13,40],[-8,58],[-2,66],[11,2],[17,96],[20,94],[11,88],[-6,89],[8,49],[-3,72],[16,73],[5,114],[9,123],[9,132],[-2,96],[-6,84]],[[3045,3974],[14,15],[8,30]],[[8064,6161],[-24,-28],[-23,18],[0,51],[13,26],[31,17],[16,-1],[6,-23],[-12,-26],[-7,-34]],[[8628,7562],[-18,35],[-11,-33],[-43,-26],[4,-31],[-24,2],[-13,19],[-19,-42],[-30,-32],[-23,-38]],[[8451,7416],[-39,-17],[-20,-27],[-30,-17],[15,28],[-6,23],[22,40],[-15,30],[-24,-20],[-32,-41],[-17,-39],[-27,-2],[-14,-28],[15,-40],[22,-10],[1,-26],[22,-17],[31,42],[25,-23],[18,-2],[4,-31],[-39,-16],[-13,-32],[-27,-30],[-14,-41],[30,-33],[11,-58],[17,-54],[18,-45],[0,-44],[-17,-16],[6,-32],[17,-18],[-5,-48],[-7,-47],[-15,-5],[-21,-64],[-22,-78],[-26,-70],[-38,-55],[-39,-50],[-31,-6],[-17,-27],[-10,20],[-15,-30],[-39,-29],[-29,-9],[-10,-63],[-15,-3],[-8,43],[7,22],[-37,19],[-13,-9]],[[8001,6331],[-28,15],[-14,24],[5,34],[-26,11],[-13,22],[-24,-31],[-27,-7],[-22,0],[-15,-14]],[[7837,6385],[-14,-9],[4,-68],[-15,2],[-2,14]],[[7810,6324],[-1,24],[-20,-17],[-12,11],[-21,22],[8,49],[-18,12],[-6,54],[-30,-10],[4,70],[26,50],[1,48],[-1,46],[-12,14],[-9,35],[-16,-5]],[[7703,6727],[-30,9],[9,25],[-13,36],[-20,-24],[-23,14],[-32,-37],[-25,-44],[-23,-8]],[[7466,6670],[-2,47],[-17,-13]],[[7447,6704],[-32,6],[-32,14],[-22,26],[-22,11],[-9,29],[-16,8],[-28,39],[-22,18],[-12,-14]],[[7252,6841],[-38,41],[-28,37],[-7,65],[20,-7],[1,30],[-12,30],[3,48],[-30,69]],[[7161,7154],[-45,24],[-8,46],[-21,27]],[[7082,7268],[-4,34],[1,23],[-17,13],[-9,-6],[-7,55]],[[7046,7387],[8,13],[-4,14],[26,28],[20,12],[29,-8],[11,38],[35,7],[10,23],[44,32],[4,13]],[[7229,7559],[-2,34],[19,15],[-25,103],[55,24],[14,13],[20,106],[55,-20],[15,27],[2,59],[23,6],[21,39]],[[7426,7965],[11,5]],[[7437,7970],[7,-41],[23,-32],[40,-22],[19,-47],[-10,-70],[10,-25],[33,-10],[37,-8],[33,-37],[18,-7],[12,-54],[17,-35],[30,1],[58,-13],[36,8],[28,-9],[41,-36],[34,0],[12,-18],[32,32],[45,20],[42,2],[32,21],[20,32],[20,20],[-5,19],[-9,23],[15,38],[15,-5],[29,-12],[28,31],[42,23],[20,39],[20,17],[40,8],[22,-7],[3,21],[-25,41],[-22,19],[-22,-22],[-27,10],[-16,-8],[-7,24],[20,59],[13,45]],[[8240,8005],[34,-23],[39,38],[-1,26],[26,62],[15,19],[0,33],[-16,14],[23,29],[35,11],[37,2],[41,-18],[25,-22],[17,-59],[10,-26],[10,-36],[10,-58],[49,-19],[32,-42],[12,-55],[42,0],[24,23],[46,17],[-15,-53],[-11,-21],[-9,-65],[-19,-58],[-33,11],[-24,-21],[7,-51],[-4,-69],[-14,-2],[0,-30]],[[4920,5353],[-12,-1],[-20,12],[-18,-1],[-33,-10],[-19,-18],[-27,-21],[-6,1]],[[4785,5315],[2,49],[3,7],[-1,24],[-12,24],[-8,4],[-8,17],[6,26],[-3,28],[1,18]],[[4765,5512],[5,0],[1,25],[-2,12],[3,8],[10,7],[-7,47],[-6,25],[2,20],[5,4]],[[4776,5660],[4,6],[8,-9],[21,-1],[5,18],[5,-1],[8,6],[4,-25],[7,7],[11,9]],[[4921,5627],[7,-84],[-11,-50],[-8,-66],[12,-51],[-1,-23]],[[5363,5191],[-4,4],[-16,-8],[-17,8],[-13,-4]],[[5313,5191],[-45,1]],[[5268,5192],[4,47],[-11,39],[-13,10],[-6,27],[-7,8],[1,16]],[[5236,5339],[7,42],[13,57],[8,1],[17,34],[10,1],[16,-24],[19,20],[2,25],[7,23],[4,30],[15,25],[5,41],[6,13],[4,31],[7,37],[24,46],[1,20],[3,10],[-11,24]],[[5393,5795],[1,19],[8,3]],[[5402,5817],[11,-38],[2,-39],[-1,-39],[15,-54],[-15,1],[-8,-4],[-13,6],[-6,-28],[16,-35],[13,-10],[3,-24],[9,-41],[-4,-16]],[[5444,5191],[-2,-31],[-22,14],[-22,15],[-35,2]],[[5856,5265],[-2,-69],[11,-8],[-9,-21],[-10,-16],[-11,-31],[-6,-27],[-1,-48],[-7,-22],[0,-45]],[[5821,4978],[-8,-16],[-1,-35],[-4,-5],[-2,-32]],[[5814,4792],[5,-55],[-2,-30],[5,-35],[16,-33],[15,-74]],[[5853,4565],[-11,6],[-37,-10],[-7,-7],[-8,-38],[6,-26],[-5,-70],[-3,-59],[7,-11],[19,-23],[8,11],[2,-64],[-21,1],[-11,32],[-10,25],[-22,9],[-6,31],[-17,-19],[-22,8],[-10,27],[-17,6],[-13,-2],[-2,19],[-9,1]],[[5342,4697],[-4,18]],[[5360,4775],[8,-6],[9,23],[15,-1],[2,-17],[11,-10],[16,37],[16,29],[7,19],[-1,48],[12,58],[13,30],[18,29],[3,18],[1,22],[5,21],[-2,33],[4,52],[5,37],[8,32],[2,36]],[[5760,5367],[17,-49],[12,-7],[8,10],[12,-4],[16,12],[6,-25],[25,-39]],[[5330,4760],[-22,62]],[[5308,4822],[21,33],[-11,39],[10,15],[19,7],[2,26],[15,-28],[24,-2],[9,27],[3,40],[-3,46],[-13,35],[12,68],[-7,12],[-21,-5],[-7,31],[2,25]],[[2906,5049],[-12,14],[-14,19],[-7,-9],[-24,8],[-7,25],[-5,-1],[-28,34]],[[2809,5139],[-3,18],[10,5],[-1,29],[6,22],[14,4],[12,37],[10,31],[-10,14],[5,34],[-6,54],[6,16],[-4,50],[-12,31]],[[2836,5484],[4,29],[9,-4],[5,17],[-6,35],[3,9]],[[2851,5570],[14,-2],[21,41],[12,6],[0,20],[5,50],[16,27],[17,1],[3,13],[21,-5],[22,30],[11,13],[14,28],[9,-3],[8,-16],[-6,-20]],[[3018,5753],[-18,-10],[-7,-29],[-10,-17],[-8,-22],[-4,-42],[-8,-35],[15,-4],[3,-27],[6,-13],[3,-24],[-4,-22],[1,-12],[7,-5],[7,-20],[36,5],[16,-7],[19,-51],[11,6],[20,-3],[16,7],[10,-10],[-5,-32],[-6,-20],[-2,-42],[5,-40],[8,-17],[1,-13],[-14,-30],[10,-13],[8,-21],[8,-58]],[[3058,4804],[-14,31],[-8,1],[18,61],[-21,27],[-17,-5],[-10,10],[-15,-15],[-21,7],[-16,62],[-13,15],[-9,28],[-19,28],[-7,-5]],[[2695,5543],[-15,14],[-6,12],[4,10],[-1,13],[-8,14],[-11,12],[-10,8],[-1,17],[-8,10],[2,-17],[-5,-14],[-7,17],[-9,5],[-4,12],[1,18],[3,19],[-8,8],[7,12]],[[2619,5713],[4,7],[18,-15],[7,7],[9,-5],[4,-12],[8,-4],[7,13]],[[2676,5704],[7,-32],[11,-24],[13,-25]],[[2707,5623],[-11,-6],[0,-23],[6,-9],[-4,-7],[1,-11],[-2,-12],[-2,-12]],[[2715,6427],[23,-4],[22,0],[26,-21],[11,-21],[26,6],[10,-13],[24,-37],[17,-27],[9,1],[17,-12],[-2,-17],[20,-2],[21,-24],[-3,-14],[-19,-7],[-18,-3],[-19,4],[-40,-5],[18,32],[-11,16],[-18,4],[-9,17],[-7,33],[-16,-2],[-26,16],[-8,12],[-36,10],[-10,11],[11,15],[-28,3],[-20,-31],[-11,-1],[-4,-14],[-14,-7],[-12,6],[15,18],[6,22],[13,13],[14,11],[21,6],[7,6]],[[5909,7133],[2,1],[4,14],[20,-1],[25,18],[-19,-25],[2,-11]],[[5943,7129],[-3,2],[-5,-5],[-4,1],[-2,-2],[0,6],[-2,4],[-6,0],[-7,-5],[-5,3]],[[5943,7129],[1,-5],[-28,-24],[-14,8],[-7,23],[14,2]],[[5377,7945],[-16,25],[-14,15],[-3,25],[-5,17],[21,13],[10,15],[20,11],[7,11],[7,-6],[13,6]],[[5417,8077],[13,-19],[21,-5],[-2,-17],[15,-12],[4,15],[19,-6],[3,-19],[20,-3],[13,-29]],[[5523,7982],[-8,0],[-4,-11],[-7,-3],[-2,-13],[-5,-3],[-1,-5],[-9,-7],[-12,1],[-4,-13]],[[5275,8306],[1,-23],[28,-14],[-1,-21],[29,11],[15,16],[32,-23],[13,-19]],[[5392,8233],[6,-30],[-8,-16],[11,-21],[6,-31],[-2,-21],[12,-37]],[[5207,7871],[3,42],[14,40],[-40,11],[-13,16]],[[5171,7980],[2,26],[-6,13]],[[5171,8059],[-5,62],[17,0],[7,22],[6,54],[-5,20]],[[5191,8217],[6,13],[23,3],[5,-13],[19,29],[-6,22],[-2,34]],[[5236,8305],[21,-8],[18,9]],[[6196,5808],[7,-19],[-1,-24],[-16,-14],[12,-16]],[[6198,5735],[-10,-32]],[[6188,5703],[-7,11],[-6,-5],[-16,1],[0,18],[-2,17],[9,27],[10,26]],[[6176,5798],[12,-5],[8,15]],[[5352,8343],[-17,-48],[-29,33],[-4,25],[41,19],[9,-29]],[[5236,8305],[-11,32],[-1,61],[5,16],[8,17],[24,4],[10,16],[22,17],[-1,-30],[-8,-20],[4,-16],[15,-9],[-7,-22],[-8,6],[-20,-42],[7,-29]],[[3008,6222],[3,10],[22,0],[16,-15],[8,1],[5,-21],[15,1],[-1,-17],[12,-2],[14,-22],[-10,-24],[-14,13],[-12,-3],[-9,3],[-5,-11],[-11,-3],[-4,14],[-10,-8],[-11,-41],[-7,10],[-1,17]],[[3008,6124],[0,16],[-7,17],[7,10],[2,23],[-2,32]],[[5333,6444],[-95,-112],[-81,-117],[-39,-26]],[[5118,6189],[-31,-6],[0,38],[-13,10],[-17,16],[-7,28],[-94,129],[-93,129]],[[4863,6533],[-105,143]],[[4758,6676],[1,11],[0,4]],[[4759,6691],[0,70],[44,44],[28,9],[23,16],[11,29],[32,24],[1,44],[16,5],[13,22],[36,9],[5,23],[-7,13],[-10,62],[-1,36],[-11,38]],[[4939,7135],[27,32],[30,11],[17,24],[27,18],[47,11],[46,4],[14,-8],[26,23],[30,0],[11,-13],[19,3]],[[5233,7240],[-5,-30],[4,-56],[-6,-49],[-18,-33],[3,-45],[23,-35],[0,-14],[17,-24],[12,-106]],[[5263,6848],[9,-52],[1,-28],[-5,-48],[2,-27],[-3,-32],[2,-37],[-11,-25],[17,-43],[1,-25],[10,-33],[13,11],[22,-28],[12,-37]],[[2769,4856],[15,45],[-6,25],[-11,-27],[-16,26],[5,16],[-4,54],[9,9],[5,37],[11,38],[-2,24],[15,13],[19,23]],[[2906,5049],[4,-45],[-9,-39],[-30,-62],[-33,-23],[-17,-51],[-6,-40],[-15,-24],[-12,29],[-11,7],[-12,-5],[-1,22],[8,14],[-3,24]],[[5969,6800],[-7,-23],[-6,-45],[-8,-31],[-6,-10],[-10,19],[-12,26],[-20,85],[-3,-5],[12,-63],[17,-59],[21,-92],[10,-32],[9,-34],[25,-65],[-6,-10],[1,-39],[33,-53],[4,-12]],[[6023,6357],[-110,0],[-107,0],[-112,0]],[[5694,6357],[0,218],[0,210],[-8,47],[7,37],[-5,25],[10,29]],[[5698,6923],[37,0],[27,-15],[28,-18],[13,-9],[21,19],[11,17],[25,5],[20,-8],[7,-29],[7,19],[22,-14],[22,-3],[13,15]],[[5951,6902],[18,-102]],[[6176,5798],[-10,20],[-11,34],[-12,19],[-8,21],[-24,23],[-19,1],[-7,12],[-16,-14],[-17,27],[-8,-44],[-33,13]],[[6011,5910],[-3,23],[12,87],[3,39],[9,18],[20,10],[14,34]],[[6066,6121],[16,-69],[8,-54],[15,-29],[38,-55],[16,-34],[15,-34],[8,-20],[14,-18]],[[4749,7532],[1,42],[-11,25],[39,43],[34,-11],[37,1],[30,-10],[23,3],[45,-2]],[[4947,7623],[11,-23],[51,-27],[10,13],[31,-27],[32,8]],[[5082,7567],[2,-35],[-26,-39],[-36,-12],[-2,-20],[-18,-33],[-10,-48],[11,-34],[-16,-26],[-6,-39],[-21,-11],[-20,-46],[-35,-1],[-27,1],[-17,-21],[-11,-22],[-13,5],[-11,20],[-8,34],[-26,9]],[[4792,7249],[-2,20],[10,22],[4,16],[-9,17],[7,39],[-11,36],[12,5],[1,27],[5,9],[0,46],[13,16],[-8,30],[-16,2],[-5,-8],[-16,0],[-7,29],[-11,-8],[-10,-15]],[[5675,8472],[3,35],[-10,-8],[-18,21],[-2,34],[35,17],[35,8],[30,-10],[29,2]],[[5777,8571],[4,-10],[-20,-34],[8,-55],[-12,-19]],[[5757,8453],[-22,0],[-24,22],[-13,7],[-23,-10]],[[6188,5703],[-6,-21],[10,-32],[10,-29],[11,-21],[90,-70],[24,0]],[[6327,5530],[-79,-177],[-36,-3],[-25,-41],[-17,-1],[-8,-19]],[[6162,5289],[-19,0],[-11,20],[-26,-25],[-8,-24],[-18,4],[-6,7],[-7,-1],[-9,0],[-35,50],[-19,0],[-10,20],[0,33],[-14,10]],[[5980,5383],[-17,64],[-12,14],[-5,23],[-14,29],[-17,4],[9,34],[15,2],[4,18]],[[5943,5571],[0,53]],[[5943,5624],[8,62],[13,16],[3,24],[12,45],[17,30],[11,58],[4,51]],[[5794,9138],[-4,-42],[42,-39],[-26,-45],[33,-67],[-19,-51],[25,-43],[-11,-39],[41,-40],[-11,-31],[-25,-34],[-60,-75]],[[5779,8632],[-50,-5],[-49,-21],[-45,-13],[-16,32],[-27,20],[6,58],[-14,53],[14,35],[25,37],[63,64],[19,12],[-3,25],[-39,28]],[[5663,8957],[-9,23],[-1,91],[-43,40],[-37,29]],[[5573,9140],[17,16],[30,-32],[37,3],[30,-14],[26,26],[14,44],[43,20],[35,-24],[-11,-41]],[[9954,4033],[9,-17],[-4,-31],[-17,-8],[-16,7],[-2,26],[10,21],[13,-8],[7,10]],[[0,4079],[9981,-14],[-17,-13],[-4,23],[14,12],[9,3],[-9983,18]],[[0,4108],[0,-29]],[[0,4108],[6,3],[-4,-28],[-2,-4]],[[3300,1994],[33,36],[24,-15],[16,24],[22,-27],[-8,-21],[-37,-17],[-13,20],[-23,-26],[-14,26]],[[5265,7548],[-9,-46],[-13,12],[-6,40],[5,22],[18,22],[5,-50]],[[5157,7984],[6,-6],[8,2]],[[5190,7775],[-2,-17],[9,-22],[-10,-18],[7,-46],[15,-8],[-3,-25]],[[5206,7639],[-25,-34],[-55,16],[-40,-19],[-4,-35]],[[4947,7623],[14,35],[5,118],[-28,62],[-21,30],[-42,23],[-3,43],[36,12],[47,-15],[-9,67],[26,-25],[65,46],[8,48],[24,12]],[[3485,5194],[7,25],[3,27]],[[3495,5246],[4,26],[-10,34]],[[3489,5306],[-3,41],[15,51]],[[3501,5398],[9,-7],[21,-14],[29,-50],[5,-24]],[[5308,4822],[-29,60],[-18,49],[-17,61],[1,19],[6,19],[7,43],[5,44]],[[5263,5117],[10,4],[40,-1],[0,71]],[[4827,8240],[-21,12],[-17,-1],[6,32],[-6,32]],[[4789,8315],[23,2],[30,-37],[-15,-40]],[[4916,8521],[-30,-63],[29,8],[30,-1],[-7,-48],[-25,-53],[29,-4],[2,-6],[25,-69],[19,-10],[17,-67],[8,-24],[33,-11],[-3,-38],[-14,-17],[11,-30],[-25,-31],[-37,0],[-48,-16],[-13,12],[-18,-28],[-26,7],[-19,-23],[-15,12],[41,62],[25,13],[-1,0],[-43,9],[-8,24],[29,18],[-15,32],[5,39],[42,-6],[4,35],[-19,36],[0,1],[-34,10],[-7,16],[10,27],[-9,16],[-15,-28],[-1,57],[-14,30],[10,61],[21,48],[23,-4],[33,4]],[[6154,7511],[4,26],[-7,40],[-16,22],[-16,6],[-10,19]],[[6109,7624],[4,6],[23,-10],[41,-9],[38,-28],[5,-11],[17,9],[25,-13],[9,-24],[17,-13]],[[6210,7485],[-27,29],[-29,-3]],[[5029,5408],[-44,-35],[-15,-20],[-25,-17],[-25,17]],[[5e3,5708],[-2,-18],[12,-30],[0,-43],[2,-47],[7,-21],[-6,-54],[2,-29],[8,-37],[6,-21]],[[4765,5512],[-8,1],[-5,-24],[-8,1],[-6,12],[2,24],[-11,36],[-8,-7],[-6,-1]],[[4715,5554],[-7,-3],[0,21],[-4,16],[0,17],[-6,25],[-7,21],[-23,0],[-6,-11],[-8,-1],[-4,-13],[-4,-17],[-14,-26]],[[4632,5583],[-13,35],[-10,24],[-8,7],[-6,12],[-4,26],[-4,13],[-8,10]],[[4579,5710],[13,29],[8,-2],[7,10],[6,0],[5,8],[-3,20],[3,6],[1,20]],[[4619,5801],[13,-1],[20,-14],[6,1],[3,7],[15,-5],[4,4]],[[4680,5793],[1,-22],[5,0],[7,8],[5,-2],[7,-15],[12,-5],[8,13],[9,8],[6,8],[6,-1],[6,-13],[3,-17],[12,-24],[-6,-16],[-1,-19],[6,6],[3,-7],[-1,-17],[8,-18]],[[4532,5834],[3,27]],[[4535,5861],[31,1],[6,14],[9,1],[11,-14],[8,-1],[9,10],[6,-17],[-12,-13],[-12,1],[-12,13],[-10,-14],[-5,-1],[-7,-8],[-25,1]],[[4579,5710],[-15,24],[-11,4],[-7,17],[1,9],[-9,13],[-2,12]],[[4536,5789],[15,10],[9,-2],[8,7],[51,-3]],[[5263,5117],[-5,9],[10,66]],[[5658,7167],[15,-20],[22,3],[20,-4],[0,-10],[15,7],[-4,-18],[-40,-5],[1,10],[-34,12],[5,25]],[[5723,7469],[-17,2],[-14,6],[-34,-16],[19,-33],[-14,-10],[-15,0],[-15,31],[-5,-13],[6,-36],[14,-27],[-10,-13],[15,-27],[14,-18],[0,-33],[-25,16],[8,-30],[-18,-7],[11,-52],[-19,-1],[-23,26],[-10,47],[-5,40],[-11,27],[-14,34],[-2,16]],[[5583,7470],[18,6],[11,13],[15,-2],[5,11],[5,2]],[[5725,7529],[13,-16],[-8,-37],[-7,-7]],[[3701,9939],[93,35],[97,-2],[36,21],[98,6],[222,-7],[174,-47],[-52,-23],[-106,-3],[-150,-5],[14,-11],[99,7],[83,-21],[54,18],[23,-21],[-30,-34],[71,22],[135,23],[83,-12],[15,-25],[-113,-42],[-16,-14],[-88,-10],[64,-3],[-32,-43],[-23,-38],[1,-66],[33,-38],[-43,-3],[-46,-19],[52,-31],[6,-50],[-30,-6],[36,-50],[-61,-5],[32,-24],[-9,-20],[-39,-10],[-39,0],[35,-40],[0,-26],[-55,24],[-14,-15],[37,-15],[37,-36],[10,-48],[-49,-11],[-22,22],[-34,34],[10,-40],[-33,-31],[73,-2],[39,-3],[-75,-52],[-75,-46],[-81,-21],[-31,0],[-29,-23],[-38,-62],[-60,-42],[-19,-2],[-37,-15],[-40,-13],[-24,-37],[0,-41],[-15,-39],[-45,-47],[11,-47],[-12,-48],[-14,-58],[-39,-4],[-41,49],[-56,0],[-27,32],[-18,58],[-49,73],[-14,39],[-3,53],[-39,54],[10,44],[-18,21],[27,69],[42,22],[11,25],[6,46],[-32,-21],[-15,-9],[-25,-8],[-34,19],[-2,40],[11,31],[25,1],[57,-15],[-48,37],[-24,20],[-28,-8],[-23,15],[31,55],[-17,22],[-22,41],[-34,62],[-35,23],[0,25],[-74,34],[-59,5],[-74,-3],[-68,-4],[-32,19],[-49,37],[73,19],[56,3],[-119,15],[-62,24],[3,23],[106,28],[101,29],[11,21],[-75,22],[24,23],[97,41],[40,7],[-12,26],[66,16],[86,9],[85,1],[30,-19],[74,33],[66,-22],[39,-5],[58,-19],[-66,32],[4,25]],[[2497,5869],[-14,10],[-17,1],[-13,12],[-15,24]],[[2438,5916],[1,18],[3,13],[-4,12],[13,48],[36,0],[1,20],[-5,4],[-3,12],[-10,14],[-11,20],[13,0],[0,33],[26,0],[26,0]],[[2529,5996],[10,-11],[2,9],[8,-7]],[[2549,5987],[-13,-23],[-13,-16],[-2,-12],[2,-11],[-5,-15]],[[2518,5910],[-7,-4],[2,-7],[-6,-6],[-9,-15],[-1,-9]],[[3340,5552],[18,-22],[17,-38],[1,-31],[10,-1],[15,-29],[11,-21]],[[3412,5410],[-4,-53],[-17,-15],[1,-14],[-5,-31],[13,-42],[9,-1],[3,-33],[17,-51]],[[3313,5365],[-19,45],[7,16],[0,27],[17,10],[7,11],[-10,22],[3,21],[22,35]],[[2574,5825],[-5,18],[-8,5]],[[2561,5848],[2,24],[-4,6],[-6,4],[-12,-7],[-1,8],[-8,10],[-6,12],[-8,5]],[[2549,5987],[3,-3],[6,11],[8,1],[3,-5],[4,3],[13,-6],[13,2],[9,6],[3,7],[9,-3],[6,-4],[8,1],[5,5],[13,-8],[4,-1],[9,-11],[8,-13],[10,-9],[7,-17]],[[2690,5943],[-9,2],[-4,-8],[-10,-8],[-7,0],[-6,-8],[-6,3],[-4,9],[-3,-2],[-4,-14],[-3,1],[0,-12],[-10,-17],[-5,-7],[-3,-7],[-8,12],[-6,-16],[-6,1],[-6,-2],[0,-29],[-4,0],[-3,-14],[-9,-2]],[[5522,7770],[7,-23],[9,-17],[-11,-22]],[[5515,7577],[-3,-10]],[[5512,7567],[-26,22],[-16,21],[-26,18],[-23,43],[6,5],[-13,25],[-1,19],[-17,10],[-9,-26],[-8,20],[0,21],[1,1]],[[5380,7746],[20,-2],[5,9],[9,-9],[11,-1],[0,16],[10,6],[2,24],[23,16]],[[5460,7805],[8,-7],[21,-26],[23,-11],[10,9]],[[3008,6124],[-19,10],[-13,-5],[-17,5],[-13,-11],[-15,18],[3,19],[25,-8],[21,-5],[10,13],[-12,26],[0,23],[-18,9],[7,16],[17,-3],[24,-9]],[[5471,7900],[14,-15],[10,-6],[24,7],[2,12],[11,2],[14,9],[3,-4],[13,8],[6,13],[9,4],[30,-18],[6,6]],[[5613,7918],[15,-16],[2,-16]],[[5630,7886],[-17,-12],[-13,-40],[-17,-40],[-22,-11]],[[5561,7783],[-17,2],[-22,-15]],[[5460,7805],[-6,20],[-4,0]],[[8352,4453],[-11,-2],[-37,42],[26,11],[14,-18],[10,-17],[-2,-16]],[[8471,4532],[2,-11],[1,-18]],[[8474,4503],[-18,-45],[-24,-13],[-3,8],[2,20],[12,36],[28,23]],[[8274,4579],[10,-16],[17,5],[7,-25],[-32,-12],[-19,-8],[-15,1],[10,34],[15,0],[7,21]],[[8413,4579],[-4,-32],[-42,-17],[-37,7],[0,22],[22,12],[18,-18],[18,5],[25,21]],[[8017,4657],[53,-6],[6,25],[51,-29],[10,-38],[42,-11],[34,-35],[-31,-23],[-31,24],[-25,-1],[-29,4],[-26,11],[-32,22],[-21,6],[-11,-7],[-51,24],[-5,25],[-25,5],[19,56],[34,-3],[22,-23],[12,-5],[4,-21]],[[8741,4690],[-14,-40],[-3,45],[5,21],[6,20],[7,-17],[-1,-29]],[[8534,4853],[-11,-19],[-19,10],[-5,26],[28,3],[7,-20]],[[8623,4875],[10,-45],[-23,24],[-23,5],[-16,-4],[-19,2],[6,33],[35,2],[30,-17]],[[8916,4904],[0,-193],[1,-192]],[[8917,4519],[-25,48],[-28,12],[-7,-17],[-35,-1],[12,48],[17,16],[-7,64],[-14,50],[-53,50],[-23,5],[-42,54],[-8,-28],[-11,-5],[-6,21],[0,26],[-21,29],[29,21],[20,-1],[-2,16],[-41,0],[-11,35],[-25,11],[-11,29],[37,14],[14,20],[45,-25],[4,-22],[8,-95],[29,-35],[23,62],[32,36],[25,0],[23,-21],[21,-21],[30,-11]],[[8478,5141],[-22,-58],[-21,-12],[-27,12],[-46,-3],[-24,-8],[-4,-45],[24,-53],[15,27],[52,20],[-2,-27],[-12,9],[-12,-35],[-25,-23],[27,-76],[-5,-20],[25,-68],[-1,-39],[-14,-17],[-11,20],[13,49],[-27,-23],[-7,16],[3,23],[-20,35],[3,57],[-19,-18],[2,-69],[1,-84],[-17,-9],[-12,18],[8,54],[-4,57],[-12,1],[-9,40],[12,39],[4,47],[14,89],[5,24],[24,44],[22,-18],[35,-8],[32,3],[27,43],[5,-14]],[[8574,5124],[-2,-51],[-14,6],[-4,-36],[11,-32],[-8,-7],[-11,38],[-8,75],[6,47],[9,22],[2,-32],[16,-5],[3,-25]],[[8045,5176],[5,-39],[19,-34],[18,12],[18,-4],[16,30],[13,5],[26,-17],[23,13],[14,82],[11,21],[10,67],[32,0],[24,-10]],[[8274,5302],[-16,-53],[20,-56],[-5,-28],[32,-54],[-33,-7],[-10,-40],[2,-54],[-27,-40],[-1,-59],[-10,-91],[-5,21],[-31,-26],[-11,36],[-20,3],[-14,19],[-33,-21],[-10,29],[-18,-4],[-23,7],[-4,79],[-14,17],[-13,50],[-4,52],[3,55],[16,39]],[[7939,4712],[-31,-1],[-24,49],[-35,48],[-12,36],[-21,48],[-14,44],[-21,83],[-24,49],[-9,51],[-10,46],[-25,37],[-14,51],[-21,33],[-29,65],[-3,30],[18,-2],[43,-12],[25,-57],[21,-40],[16,-25],[26,-63],[28,-1],[23,-41],[16,-49],[22,-27],[-12,-49],[16,-20],[10,-2],[5,-41],[10,-33],[20,-5],[14,-37],[-7,-74],[-1,-91]],[[7252,6841],[-17,-27],[-11,-55],[27,-23],[26,-29],[36,-33],[38,-8],[16,-30],[22,-5],[33,-14],[23,1],[4,23],[-4,38],[2,25]],[[7703,6727],[2,-22],[-10,-11],[2,-36],[-19,10],[-36,-41],[0,-33],[-15,-50],[-1,-29],[-13,-48],[-21,13],[-1,-61],[-7,-20],[3,-25],[-14,-14]],[[7472,6360],[-4,-21],[-19,1],[-34,-13],[2,-44],[-15,-35],[-40,-40],[-31,-69],[-21,-38],[-28,-38],[0,-27],[-13,-15],[-26,-21],[-12,-3],[-9,-45],[6,-77],[1,-49],[-11,-56],[0,-101],[-15,-2],[-12,-46],[8,-19],[-25,-17],[-10,-40],[-11,-17],[-26,55],[-13,83],[-11,60],[-9,28],[-15,56],[-7,74],[-5,37],[-25,81],[-12,115],[-8,75],[0,72],[-5,55],[-41,-35],[-19,7],[-36,71],[13,22],[-8,23],[-33,50]],[[6893,6457],[19,40],[61,-1],[-6,51],[-15,30],[-4,46],[-18,26],[31,62],[32,-4],[29,61],[18,60],[27,60],[-1,42],[24,34],[-23,29],[-9,40],[-10,52],[14,25],[42,-14],[31,9],[26,49]],[[4827,8240],[5,-42],[-21,-53],[-49,-35],[-40,9],[23,62],[-15,60],[38,46],[21,28]],[[6497,7255],[25,12],[19,33],[19,-1],[12,11],[20,-6],[31,-30],[22,-6],[31,-53],[21,-2],[3,-49]],[[6690,6820],[14,-31],[11,-36],[27,-26],[1,-52],[13,-10],[2,-27],[-40,-30],[-10,-69]],[[6708,6539],[-53,18],[-30,13],[-31,8],[-12,73],[-13,10],[-22,-11],[-28,-28],[-34,20],[-28,45],[-27,17],[-18,56],[-21,79],[-15,-10],[-17,20],[-11,-24]],[[6348,6825],[-15,32],[0,31],[-9,0],[5,43],[-15,45],[-34,32],[-19,56],[6,46],[14,21],[-2,34],[-18,18],[-18,70]],[[6243,7253],[-15,48],[5,18],[-8,68],[19,17]],[[6357,7321],[9,-43],[26,-13],[20,-29],[39,-10],[44,15],[2,14]],[[6348,6825],[-16,3]],[[6332,6828],[-19,5],[-20,-56]],[[6293,6777],[-52,4],[-78,119],[-41,41],[-34,16]],[[6088,6957],[-11,72]],[[6077,7029],[61,62],[11,71],[-3,43],[16,15],[14,37]],[[6176,7257],[12,9],[32,-8],[10,-15],[13,10]],[[4597,8984],[-7,-39],[31,-40],[-36,-45],[-80,-41],[-24,-10],[-36,8],[-78,19],[28,26],[-61,29],[49,12],[-1,17],[-58,14],[19,38],[42,9],[43,-40],[42,32],[35,-17],[45,32],[47,-4]],[[5992,6990],[-5,-19]],[[5987,6971],[-10,8],[-6,-39],[7,-7],[-7,-8],[-1,-15],[13,8]],[[5983,6918],[0,-23],[-14,-95]],[[5951,6902],[8,19],[-2,4],[8,27],[5,45],[4,15],[1,0]],[[5975,7012],[9,0],[3,11],[7,0]],[[5994,7023],[1,-24],[-4,-9],[1,0]],[[5431,7316],[-10,-46],[4,-19],[-6,-30],[-21,22],[-14,7],[-39,30],[4,30],[32,-6],[28,7],[22,5]],[[5255,7492],[17,-42],[-4,-78],[-13,4],[-11,-20],[-10,16],[-2,71],[-6,34],[15,-3],[14,18]],[[5383,7805],[-3,-29],[7,-25]],[[5387,7751],[-22,8],[-23,-20],[1,-30],[-3,-17],[9,-30],[26,-29],[14,-49],[31,-48],[22,0],[7,-13],[-8,-11],[25,-22],[20,-18],[24,-30],[3,-11],[-5,-22],[-16,28],[-24,10],[-12,-39],[20,-21],[-3,-31],[-11,-4],[-15,-50],[-12,-5],[0,18],[6,32],[6,12],[-11,35],[-8,29],[-12,8],[-8,25],[-18,11],[-12,24],[-21,4],[-21,26],[-26,39],[-19,34],[-8,58],[-14,7],[-23,20],[-12,-8],[-16,-28],[-12,-4]],[[2845,6150],[19,-5],[14,-15],[5,-16],[-19,-1],[-9,-10],[-15,10],[-16,21],[3,14],[12,4],[6,-2]],[[5992,6990],[31,-24],[54,63]],[[6088,6957],[-5,-8],[-56,-30],[28,-59],[-9,-10],[-5,-20],[-21,-8],[-7,-21],[-12,-19],[-31,10]],[[5970,6792],[-1,8]],[[5983,6918],[4,17],[0,36]],[[8739,7075],[4,-20],[-16,-36],[-11,19],[-15,-14],[-7,-34],[-18,16],[0,28],[15,36],[16,-7],[12,25],[20,-13]],[[8915,7252],[-10,-47],[4,-30],[-14,-42],[-35,-27],[-49,-4],[-40,-67],[-19,22],[-1,44],[-48,-13],[-33,-27],[-32,-2],[28,-43],[-19,-101],[-18,-24],[-13,23],[7,53],[-18,17],[-11,41],[26,18],[15,37],[28,30],[20,41],[55,17],[30,-12],[29,105],[19,-28],[40,59],[16,23],[18,72],[-5,67],[11,37],[30,11],[15,-82],[-1,-48],[-25,-59],[0,-61]],[[8997,7667],[19,-12],[20,25],[6,-67],[-41,-16],[-25,-59],[-43,41],[-15,-65],[-31,-1],[-4,59],[14,46],[29,3],[8,82],[9,46],[32,-62],[22,-20]],[[6970,7554],[-15,-10],[-37,-42],[-12,-42],[-11,0],[-7,28],[-36,2],[-5,48],[-14,0],[2,60],[-33,43],[-48,-5],[-32,-8],[-27,53],[-22,22],[-43,43],[-6,5],[-71,-35],[1,-218]],[[6554,7498],[-14,-3],[-20,46],[-18,17],[-32,-12],[-12,-20]],[[6458,7526],[-2,14],[7,25],[-5,21],[-32,20],[-13,53],[-15,15],[-1,19],[27,-6],[1,44],[23,9],[25,-9],[5,58],[-5,36],[-28,-2],[-24,14],[-32,-26],[-26,-12]],[[6363,7799],[-14,9],[3,31],[-18,39],[-20,-2],[-24,40],[16,45],[-8,12],[22,65],[29,-34],[3,43],[58,64],[43,2],[61,-41],[33,-24],[30,25],[44,1],[35,-30],[8,17],[39,-2],[7,28],[-45,40],[27,29],[-5,16],[26,15],[-20,41],[13,20],[104,21],[13,14],[70,22],[25,24],[50,-12],[9,-61],[29,14],[35,-20],[-2,-32],[27,3],[69,56],[-10,-19],[35,-46],[62,-150],[15,31],[39,-34],[39,16],[16,-11],[13,-34],[20,-12],[11,-25],[36,8],[15,-36]],[[7229,7559],[-17,9],[-14,21],[-42,6],[-46,2],[-10,-6],[-39,24],[-16,-12],[-4,-35],[-46,21],[-18,-9],[-7,-26]],[[6155,4958],[-20,-24],[-7,-24],[-10,-4],[-4,-42],[-9,-24],[-5,-39],[-12,-20]],[[6088,4781],[-40,59],[-1,35],[-101,120],[-5,6]],[[5941,5001],[0,63],[8,24],[14,39],[10,43],[-13,68],[-3,30],[-13,41]],[[5944,5309],[17,35],[19,39]],[[6162,5289],[-24,-67],[0,-215],[17,-49]],[[7046,7387],[-53,-9],[-34,19],[-30,-4],[3,34],[30,-10],[10,18]],[[6972,7435],[21,-6],[36,43],[-33,31],[-20,-15],[-21,22],[24,39],[-9,5]],[[7849,5777],[-7,72],[18,49],[36,11],[26,-8]],[[7922,5901],[23,-23],[12,40],[25,-21]],[[7982,5897],[6,-40],[-3,-71],[-47,-45],[13,-36],[-30,-4],[-24,-24]],[[7897,5677],[-23,9],[-11,30],[-14,61]],[[8564,7339],[24,-70],[7,-38],[0,-68],[-10,-33],[-25,-11],[-22,-25],[-25,-5],[-3,32],[5,45],[-13,61],[21,10],[-19,51]],[[8504,7288],[2,5],[12,-2],[11,27],[20,2],[11,4],[4,15]],[[5557,7574],[5,13]],[[5562,7587],[7,4],[4,20],[5,3],[4,-8],[5,-4],[3,-10],[5,-2],[5,-11],[4,0],[-3,-14],[-3,-7],[1,-5]],[[5599,7553],[-6,-2],[-17,-9],[-1,-12],[-4,0]],[[6332,6828],[6,-26],[-3,-13],[9,-45]],[[6344,6744],[-19,-1],[-7,28],[-25,6]],[[7922,5901],[9,26],[1,50],[-22,52],[-2,58],[-21,48],[-21,4],[-6,-20],[-16,-2],[-8,10],[-30,-35],[0,53],[7,62],[-19,3],[-2,36],[-12,18]],[[7780,6264],[6,21],[24,39]],[[7837,6385],[17,-47],[12,-54],[34,0],[11,-52],[-18,-15],[-8,-21],[34,-36],[23,-70],[17,-52],[21,-41],[7,-41],[-5,-59]],[[5975,7012],[10,49],[14,41],[0,2]],[[5999,7104],[13,-3],[4,-23],[-15,-22],[-7,-33]],[[4785,5315],[-7,0],[-29,28],[-25,45],[-24,32],[-18,38]],[[4682,5458],[6,19],[2,17],[12,33],[13,27]],[[5412,6408],[-20,-22],[-15,33],[-44,25]],[[5263,6848],[13,14],[3,25],[-3,24],[19,23],[8,19],[14,17],[2,45]],[[5319,7015],[32,-20],[12,5],[23,-10],[37,-26],[13,-53],[25,-11],[39,-25],[30,-29],[13,15],[13,27],[-6,45],[9,29],[20,28],[19,8],[37,-12],[10,-27],[10,0],[9,-10],[28,-7],[6,-19]],[[5694,6357],[0,-118],[-32,0],[0,-25]],[[5662,6214],[-111,113],[-111,113],[-28,-32]],[[7271,5502],[-4,-62],[-12,-16],[-24,-14],[-13,47],[-5,85],[13,96],[19,-33],[13,-42],[13,-61]],[[5804,3347],[10,-18],[-9,-29],[-4,-19],[-16,-9],[-5,-19],[-10,-6],[-21,46],[15,37],[15,23],[13,12],[12,-18]],[[5631,8267],[-2,15],[3,16],[-13,10],[-29,10]],[[5590,8318],[-6,50]],[[5584,8368],[32,18],[47,-4],[27,6],[4,-12],[15,-4],[26,-29]],[[5652,8242],[-7,19],[-14,6]],[[5584,8368],[1,44],[14,37],[26,20],[22,-44],[22,1],[6,46]],[[5757,8453],[14,-14],[2,-28],[9,-35]],[[4759,6691],[-4,0],[0,-31],[-17,-2],[-9,-14],[-13,0],[-10,8],[-23,-6],[-9,-46],[-9,-5],[-13,-74],[-38,-64],[-9,-81],[-12,-27],[-3,-21],[-63,-5]],[[4527,6323],[1,27],[11,17],[9,30],[-2,20],[10,42],[15,38],[9,9],[8,35],[0,31],[10,37],[19,21],[18,60],[0,1],[14,23],[26,6],[22,41],[14,16],[23,49],[-7,73],[10,51],[4,31],[18,40],[28,27],[21,25],[18,61],[9,36],[20,0],[17,-25],[26,4],[29,-13],[12,-1]],[[5739,7906],[6,9],[19,6],[20,-19],[12,-2],[12,-16],[-2,-20],[11,-9],[4,-25],[9,-15],[-2,-9],[5,-6],[-7,-4],[-16,1],[-3,9],[-6,-5],[2,-11],[-7,-19],[-5,-20],[-7,-6]],[[5784,7745],[-5,27],[3,25],[-1,26],[-16,35],[-9,25],[-9,17],[-8,6]],[[6376,4321],[7,-25],[7,-39],[4,-71],[7,-28],[-2,-28],[-5,-18],[-10,35],[-5,-18],[5,-43],[-2,-25],[-8,-14],[-1,-50],[-11,-69],[-14,-81],[-17,-112],[-11,-82],[-12,-69],[-23,-14],[-24,-25],[-16,15],[-22,21],[-8,31],[-2,53],[-10,47],[-2,42],[5,43],[13,10],[0,20],[13,45],[2,37],[-6,28],[-5,38],[-2,54],[9,33],[4,38],[14,2],[15,12],[11,10],[12,1],[16,34],[23,36],[8,30],[-4,25],[12,-7],[15,41],[1,36],[9,26],[10,-25]],[[2301,6586],[-10,-52],[-5,-43],[-2,-79],[-3,-29],[5,-32],[9,-29],[5,-45],[19,-44],[6,-34],[11,-29],[29,-16],[12,-25],[24,17],[21,6],[21,11],[18,10],[17,24],[7,34],[2,50],[5,17],[19,16],[29,13],[25,-2],[17,5],[6,-12],[-1,-29],[-15,-35],[-6,-36],[5,-10],[-4,-26],[-7,-46],[-7,15],[-6,-1]],[[2438,5916],[-32,64],[-14,19],[-23,16],[-15,-5],[-22,-22],[-14,-6],[-20,16],[-21,11],[-26,27],[-21,8],[-31,28],[-23,28],[-7,16],[-16,3],[-28,19],[-12,27],[-30,34],[-14,37],[-6,29],[9,5],[-3,17],[7,16],[0,20],[-10,27],[-2,23],[-9,30],[-25,59],[-28,46],[-13,37],[-24,24],[-5,14],[4,37],[-14,13],[-17,29],[-7,41],[-14,5],[-17,31],[-13,29],[-1,19],[-15,44],[-10,45],[1,23],[-20,23],[-10,-2],[-15,16],[-5,-24],[5,-28],[2,-45],[10,-24],[21,-41],[4,-14],[4,-4],[4,-20],[5,1],[6,-38],[8,-15],[6,-21],[17,-30],[10,-55],[8,-26],[8,-28],[1,-31],[13,-2],[12,-27],[10,-26],[-1,-11],[-12,-21],[-5,0],[-7,36],[-18,33],[-20,29],[-14,15],[1,43],[-5,32],[-13,19],[-19,26],[-4,-8],[-7,16],[-17,14],[-16,34],[2,5],[11,-4],[11,22],[1,27],[-22,42],[-16,17],[-10,36],[-11,39],[-12,47],[-12,54]],[[1746,6980],[32,4],[35,7],[-2,-12],[41,-29],[64,-41],[55,0],[22,0],[0,24],[48,0],[10,-20],[15,-19],[16,-26],[9,-31],[7,-32],[15,-18],[23,-18],[17,47],[23,1],[19,-24],[14,-40],[10,-35],[16,-34],[6,-41],[8,-28],[22,-18],[20,-13],[10,2]],[[5599,7553],[9,4],[13,1]],[[4661,5921],[10,11],[4,35],[9,1],[20,-16],[15,11],[11,-4],[4,13],[112,1],[6,42],[-5,7],[-13,255],[-14,255],[43,1]],[[5118,6189],[0,-136],[-15,-39],[-2,-37],[-25,-9],[-38,-5],[-10,-21],[-18,-3]],[[4680,5793],[1,18],[-2,23],[-11,16],[-5,34],[-2,37]],[[7737,5644],[-3,44],[9,45],[-10,35],[3,65],[-12,30],[-9,71],[-5,75],[-12,49],[-18,-30],[-32,-42],[-15,5],[-17,14],[9,73],[-6,56],[-21,68],[3,21],[-16,7],[-20,49]],[[7780,6264],[-16,-14],[-16,-26],[-20,-2],[-12,-64],[-12,-11],[14,-52],[17,-43],[12,-39],[-11,-51],[-9,-11],[6,-30],[19,-47],[3,-33],[0,-27],[11,-54],[-16,-55],[-13,-61]],[[5538,7532],[-6,4],[-8,19],[-12,12]],[[5533,7629],[8,-10],[4,-9],[9,-6],[10,-12],[-2,-5]],[[7437,7970],[29,10],[53,51],[42,28],[24,-18],[29,-1],[19,-28],[28,-2],[40,-15],[27,41],[-11,35],[28,61],[31,-24],[26,-7],[32,-15],[6,-44],[39,-25],[26,11],[36,7],[27,-7],[28,-29],[16,-30],[26,1],[35,-10],[26,15],[36,9],[41,42],[17,-6],[14,-20],[33,5]],[[5959,4377],[21,5],[34,-17],[7,8],[19,1],[10,18],[17,-1],[30,23],[22,34]],[[6119,4448],[5,-26],[-1,-59],[3,-52],[1,-92],[5,-29],[-8,-43],[-11,-41],[-18,-36],[-25,-23],[-31,-28],[-32,-64],[-10,-11],[-20,-42],[-11,-13],[-3,-42],[14,-45],[5,-35],[0,-17],[5,3],[-1,-58],[-4,-28],[6,-10],[-4,-25],[-11,-21],[-23,-20],[-34,-32],[-12,-21],[3,-25],[7,-4],[-3,-31]],[[5911,3478],[-21,0]],[[5890,3478],[-2,26],[-4,27]],[[5884,3531],[-3,21],[5,66],[-7,42],[-13,83]],[[5866,3743],[29,67],[7,43],[5,5],[3,35],[-5,17],[1,44],[6,41],[0,75],[-15,19],[-13,4],[-6,15],[-13,12],[-23,-1],[-2,22]],[[5840,4141],[-2,42],[84,49]],[[5922,4232],[16,-28],[8,5],[11,-15],[1,-23],[-6,-28],[2,-42],[19,-36],[8,41],[12,12],[-2,76],[-12,43],[-10,19],[-10,-1],[-7,77],[7,45]],[[4661,5921],[-18,41],[-17,43],[-18,16],[-13,17],[-16,-1],[-13,-12],[-14,5],[-10,-19]],[[4542,6011],[-2,32],[8,29],[3,55],[-3,59],[-3,29],[2,30],[-7,28],[-14,25]],[[4526,6298],[6,20],[108,-1],[-5,86],[7,30],[26,5],[-1,152],[91,-4],[0,90]],[[5922,4232],[-15,15],[9,55],[9,21],[-6,49],[6,48],[5,16],[-7,50],[-14,26]],[[5909,4512],[28,-11],[5,-16],[10,-28],[7,-80]],[[7836,5425],[7,-5],[16,-36],[12,-40],[2,-39],[-3,-27],[2,-21],[2,-35],[10,-16],[11,-52],[-1,-20],[-19,-4],[-27,44],[-32,47],[-4,30],[-16,39],[-4,49],[-10,32],[4,43],[-7,25]],[[7779,5439],[5,11],[23,-26],[2,-30],[18,7],[9,24]],[[8045,5176],[21,-20],[21,11],[6,50],[12,11],[33,13],[20,47],[14,37]],[[8206,5379],[22,41],[14,47],[11,0],[14,-30],[1,-26],[19,-16],[23,-18],[-2,-23],[-19,-3],[5,-29],[-20,-20]],[[5453,3369],[-20,45],[-11,43],[-6,58],[-7,42],[-9,91],[-1,71],[-3,32],[-11,25],[-15,48],[-14,71],[-6,37],[-23,58],[-2,45]],[[5644,4022],[23,14],[18,-4],[11,-13],[0,-5]],[[5552,3594],[0,-218],[-25,-30],[-15,-4],[-17,11],[-13,4],[-4,25],[-11,17],[-14,-30]],[[9604,3812],[23,-36],[14,-28],[-10,-14],[-16,16],[-19,27],[-18,31],[-19,42],[-4,20],[12,-1],[16,-20],[12,-20],[9,-17]],[[5412,6408],[7,-92],[10,-15],[1,-19],[11,-20],[-6,-25],[-11,-120],[-1,-77],[-35,-56],[-12,-78],[11,-22],[0,-38],[18,-1],[-3,-28]],[[5393,5795],[-5,-1],[-19,64],[-6,3],[-22,-33],[-21,17],[-15,3],[-8,-8],[-17,2],[-16,-25],[-14,-2],[-34,31],[-13,-15],[-14,1],[-10,23],[-28,22],[-30,-7],[-7,-13],[-4,-34],[-8,-24],[-2,-53]],[[5236,5339],[-29,-21],[-11,3],[-10,-13],[-23,1],[-15,37],[-9,43],[-19,39],[-21,-1],[-25,0]],[[2619,5713],[-10,18],[-13,24],[-6,20],[-12,19],[-13,26],[3,9],[4,-9],[2,5]],[[2690,5943],[-2,-5],[-2,-13],[3,-22],[-6,-20],[-3,-24],[-1,-26],[1,-15],[1,-27],[-4,-6],[-3,-25],[2,-15],[-6,-16],[2,-16],[4,-9]],[[5092,8091],[14,16],[24,87],[38,25],[23,-2]],[[5863,9167],[-47,-24],[-22,-5]],[[5573,9140],[-17,-2],[-4,-39],[-53,9],[-7,-33],[-27,1],[-18,-42],[-28,-66],[-43,-83],[10,-20],[-10,-24],[-27,1],[-18,-55],[2,-79],[17,-29],[-9,-70],[-23,-40],[-12,-34]],[[5306,8535],[-19,36],[-55,-69],[-37,-13],[-38,30],[-10,63],[-9,137],[26,38],[73,49],[55,61],[51,82],[66,115],[47,44],[76,74],[61,26],[46,-3],[42,49],[51,-3],[50,12],[87,-43],[-36,-16],[30,-37]],[[5686,9657],[-62,-24],[-49,13],[19,16],[-16,19],[57,11],[11,-22],[40,-13]],[[5506,9766],[92,-44],[-70,-23],[-15,-44],[-25,-11],[-13,-49],[-34,-2],[-59,36],[25,21],[-42,17],[-54,50],[-21,46],[75,21],[16,-20],[39,0],[11,21],[40,2],[35,-21]],[[5706,9808],[55,-21],[-41,-32],[-81,-7],[-82,10],[-5,16],[-40,1],[-30,27],[86,17],[40,-14],[28,17],[70,-14]],[[9805,2640],[6,-24],[20,24],[8,-25],[0,-25],[-10,-27],[-18,-44],[-14,-24],[10,-28],[-22,-1],[-23,-22],[-8,-39],[-16,-60],[-21,-26],[-14,-17],[-26,1],[-18,20],[-30,4],[-5,22],[15,43],[35,59],[18,11],[20,22],[24,31],[16,31],[13,44],[10,15],[5,33],[19,27],[6,-25]],[[9849,2922],[20,-63],[1,41],[13,-16],[4,-45],[22,-19],[19,-5],[16,22],[14,-6],[-7,-53],[-8,-34],[-22,1],[-7,-18],[3,-25],[-4,-11],[-11,-32],[-14,-41],[-21,-23],[-5,15],[-12,9],[16,48],[-9,33],[-30,23],[1,22],[20,20],[5,46],[-1,38],[-12,40],[1,10],[-13,25],[-22,52],[-12,42],[11,4],[15,-33],[21,-15],[8,-52]],[[6475,6041],[-9,41],[-22,98]],[[6444,6180],[83,59],[19,118],[-13,42]],[[6566,6530],[12,-40],[16,-22],[20,-8],[17,-10],[12,-34],[8,-20],[10,-7],[0,-13],[-10,-36],[-5,-16],[-12,-19],[-10,-41],[-13,3],[-5,-14],[-5,-30],[4,-39],[-3,-7],[-13,0],[-17,-22],[-3,-29],[-6,-12],[-18,0],[-10,-15],[0,-24],[-14,-16],[-15,5],[-19,-19],[-12,-4]],[[6557,6597],[8,20],[3,-5],[-2,-25],[-4,-10]],[[6893,6457],[-20,15],[-9,43],[-21,45],[-51,-12],[-45,-1],[-39,-8]],[[2836,5484],[-9,17],[-6,32],[7,16],[-7,4],[-5,20],[-14,16],[-12,-4],[-6,-20],[-11,-15],[-6,-2],[-3,-13],[13,-32],[-7,-7],[-4,-9],[-13,-3],[-5,35],[-4,-10],[-9,4],[-5,24],[-12,3],[-7,7],[-12,0],[-1,-13],[-3,9]],[[2707,5623],[10,-22],[-1,-12],[11,-3],[3,5],[8,-14],[13,4],[12,15],[17,12],[9,17],[16,-3],[-1,-6],[15,-2],[12,-10],[10,-18],[10,-16]],[[3045,3974],[-28,33],[-2,25],[-55,59],[-50,65],[-22,36],[-11,49],[4,17],[-23,77],[-28,109],[-26,118],[-11,27],[-9,43],[-21,39],[-20,24],[9,26],[-14,57],[9,41],[22,37]],[[8510,5555],[2,-40],[2,-33],[-9,-54],[-11,60],[-13,-30],[9,-43],[-8,-28],[-32,35],[-8,42],[8,28],[-17,28],[-9,-24],[-13,2],[-21,-33],[-4,17],[11,50],[17,17],[15,22],[10,-27],[21,17],[5,26],[19,1],[-1,46],[22,-28],[3,-30],[2,-21]],[[8443,5665],[-10,-20],[-9,-37],[-8,-17],[-17,40],[5,16],[7,17],[3,36],[16,4],[-5,-40],[21,57],[-3,-56]],[[8291,5608],[-37,-56],[14,41],[20,37],[16,41],[15,58],[5,-48],[-18,-33],[-15,-40]],[[8385,5760],[16,-18],[18,0],[0,-25],[-13,-25],[-18,-18],[-1,28],[2,30],[-4,28]],[[8485,5776],[8,-66],[-21,16],[0,-20],[7,-37],[-13,-13],[-1,42],[-9,3],[-4,36],[16,-5],[0,22],[-17,45],[27,-1],[7,-22]],[[8375,5830],[-7,-51],[-12,29],[-15,45],[24,-2],[10,-21]],[[8369,6151],[17,-17],[9,15],[2,-15],[-4,-24],[9,-43],[-7,-49],[-16,-19],[-5,-48],[7,-47],[14,-7],[13,7],[34,-32],[-2,-32],[9,-15],[-3,-27],[-22,29],[-10,31],[-7,-22],[-18,36],[-25,-9],[-14,13],[1,25],[9,15],[-8,13],[-4,-21],[-14,34],[-4,26],[-1,56],[11,-19],[3,92],[9,54],[17,0]],[[9329,4655],[-8,-6],[-12,22],[-12,38],[-6,45],[4,6],[3,-18],[8,-13],[14,-38],[13,-20],[-4,-16]],[[9221,4734],[-15,-5],[-4,-17],[-15,-14],[-15,-14],[-14,0],[-23,18],[-16,16],[2,18],[25,-8],[15,4],[5,29],[4,1],[2,-31],[16,4],[8,20],[16,21],[-4,35],[17,1],[6,-9],[-1,-33],[-9,-36]],[[8916,4904],[48,-41],[51,-34],[19,-30],[16,-30],[4,-34],[46,-37],[7,-31],[-25,-7],[6,-39],[25,-39],[18,-62],[15,2],[-1,-27],[22,-10],[-9,-11],[30,-25],[-3,-17],[-18,-4],[-7,16],[-24,6],[-28,9],[-22,38],[-16,32],[-14,52],[-36,26],[-24,-17],[-17,-20],[4,-43],[-22,-20],[-16,9],[-28,3]],[[9253,4792],[-9,-16],[-5,35],[-6,23],[-13,19],[-16,25],[-20,18],[8,14],[15,-17],[9,-13],[12,-14],[11,-25],[11,-19],[3,-30]],[[5392,8233],[19,18],[43,27],[35,20],[28,-10],[2,-14],[27,-1]],[[5546,8273],[34,-7],[51,1]],[[5653,8105],[14,-52],[-3,-17],[-14,-6],[-25,-50],[7,-26],[-6,3]],[[5626,7957],[-26,23],[-20,-8],[-13,6],[-17,-13],[-14,21],[-11,-8],[-2,4]],[[3159,6151],[14,-5],[5,-12],[-7,-15],[-21,1],[-17,-2],[-1,25],[4,9],[23,-1]],[[8628,7562],[4,-10]],[[8632,7552],[-11,3],[-12,-20],[-8,-20],[1,-42],[-14,-13],[-5,-11],[-11,-17],[-18,-10],[-12,-16],[-1,-25],[-3,-7],[11,-9],[15,-26]],[[8504,7288],[-13,11],[-4,-11],[-8,-5],[-1,11],[-7,5],[-8,10],[8,26],[7,7],[-3,11],[7,31],[-2,10],[-16,7],[-13,15]],[[4792,7249],[-11,-15],[-14,8],[-15,-6],[5,46],[-3,36],[-12,6],[-7,22],[2,39],[11,21],[2,24],[6,36],[-1,25],[-5,21],[-1,20]],[[6411,6520],[-2,43],[7,31],[8,6],[8,-18],[1,-35],[-6,-35]],[[6427,6512],[-8,-4],[-8,12]],[[5630,7886],[12,13],[17,-7],[18,0],[13,-14],[10,9],[20,5],[7,14],[12,0]],[[5784,7745],[12,-11],[13,9],[13,-10]],[[5822,7733],[0,-15],[-13,-13],[-9,6],[-7,-71]],[[5629,7671],[-5,10],[6,10],[-7,7],[-8,-13],[-17,17],[-2,25],[-17,14],[-3,18],[-15,24]],[[8989,8056],[28,-105],[-41,19],[-17,-85],[27,-61],[-1,-41],[-21,36],[-18,-46],[-5,50],[3,57],[-3,64],[6,45],[2,79],[-17,58],[3,80],[25,28],[-11,27],[13,8],[7,-39],[10,-57],[-1,-58],[11,-59]],[[5546,8273],[6,26],[38,19]],[[0,9132],[68,-45],[73,-59],[-3,-37],[19,-15],[-6,43],[75,-8],[55,-56],[-28,-26],[-46,-6],[0,-57],[-11,-13],[-26,2],[-22,21],[-36,17],[-7,26],[-28,9],[-31,-7],[-16,20],[6,22],[-33,-14],[13,-28],[-16,-25]],[[0,8896],[0,236]],[[0,9282],[9999,-40],[-30,-3],[-5,19],[-9964,24]],[[0,9282],[4,3],[23,0],[40,-17],[-2,-8],[-29,-14],[-36,-4],[0,40]],[[8988,9383],[-42,-1],[-57,7],[-5,3],[27,23],[34,6],[40,-23],[3,-15]],[[9186,9493],[-32,-23],[-44,5],[-52,23],[7,20],[51,-9],[70,-16]],[[9029,9522],[-22,-44],[-102,1],[-46,-14],[-55,39],[15,40],[37,11],[73,-2],[100,-31]],[[6598,9235],[-17,-5],[-91,8],[-7,26],[-50,16],[-4,32],[28,13],[-1,32],[55,50],[-25,7],[66,52],[-7,27],[62,31],[91,38],[93,11],[48,22],[54,8],[19,-23],[-19,-19],[-98,-29],[-85,-28],[-86,-57],[-42,-57],[-43,-57],[5,-49],[54,-49]],[[0,8896],[9963,-26],[-36,4],[25,-31],[17,-49],[13,-16],[3,-24],[-7,-16],[-52,13],[-78,-44],[-25,-7],[-42,-42],[-40,-36],[-11,-27],[-39,41],[-73,-46],[-12,22],[-27,-26],[-37,8],[-9,-38],[-33,-58],[1,-24],[31,-13],[-4,-86],[-25,-2],[-12,-49],[11,-26],[-48,-30],[-10,-67],[-41,-15],[-9,-60],[-40,-55],[-10,41],[-12,86],[-15,131],[13,82],[23,35],[2,28],[43,13],[50,75],[47,60],[50,48],[23,83],[-34,-5],[-17,-49],[-70,-65],[-23,73],[-72,-20],[-69,-99],[23,-36],[-62,-16],[-43,-6],[2,43],[-43,9],[-35,-29],[-85,10],[-91,-18],[-90,-115],[-106,-139],[43,-8],[14,-37],[27,-13],[18,30],[30,-4],[40,-65],[1,-50],[-21,-59],[-3,-71],[-12,-94],[-42,-86],[-9,-41],[-38,-69],[-38,-68],[-18,-35],[-37,-34],[-17,-1],[-17,29],[-38,-44],[-4,-19]],[[6363,7799],[-12,-35],[-27,-10],[-28,-61],[25,-56],[-2,-40],[30,-70]],[[6109,7624],[-35,49],[-32,23],[-24,34],[20,10],[23,49],[-15,24],[41,24],[-1,13],[-25,-10]],[[6061,7840],[1,26],[14,17],[27,4],[5,20],[-7,33],[12,30],[-1,18],[-41,19],[-16,-1],[-17,28],[-21,-9],[-35,20],[0,12],[-10,26],[-22,3],[-2,18],[7,12],[-18,33],[-29,-5],[-8,3],[-7,-14],[-11,3]],[[5777,8571],[31,33],[-29,28]],[[5863,9167],[29,20],[46,-35],[76,-14],[105,-67],[21,-28],[2,-40],[-31,-31],[-45,-15],[-124,44],[-21,-7],[45,-43],[2,-28],[2,-60],[36,-18],[22,-15],[3,28],[-17,26],[18,22],[67,-37],[24,15],[-19,43],[65,58],[25,-4],[26,-20],[16,40],[-23,35],[14,36],[-21,36],[78,-18],[16,-34],[-35,-7],[0,-33],[22,-20],[43,13],[7,38],[58,28],[97,50],[20,-3],[-27,-35],[35,-7],[19,21],[52,1],[42,25],[31,-36],[32,39],[-29,35],[14,19],[82,-18],[39,-18],[100,-68],[19,31],[-28,31],[-1,13],[-34,6],[10,28],[-15,46],[-1,19],[51,53],[18,54],[21,11],[74,-15],[5,-33],[-26,-48],[17,-19],[9,-41],[-6,-81],[31,-36],[-12,-40],[-55,-84],[32,-8],[11,21],[31,15],[7,29],[24,29],[-16,33],[13,39],[-31,5],[-6,33],[22,59],[-36,48],[50,40],[-7,42],[14,2],[15,-33],[-11,-57],[29,-11],[-12,43],[46,23],[58,3],[51,-34],[-25,49],[-2,63],[48,12],[67,-2],[60,7],[-23,31],[33,39],[31,2],[54,29],[74,8],[9,16],[73,6],[23,-14],[62,32],[51,-1],[8,25],[26,25],[66,25],[48,-19],[-38,-15],[63,-9],[7,-29],[25,14],[82,-1],[62,-29],[23,-22],[-7,-30],[-31,-18],[-73,-33],[-21,-17],[35,-8],[41,-15],[25,11],[14,-38],[12,15],[44,10],[90,-10],[6,-28],[116,-9],[2,46],[59,-11],[44,1],[45,-32],[13,-37],[-17,-25],[35,-47],[44,-24],[27,62],[44,-26],[48,16],[53,-18],[21,16],[45,-8],[-20,55],[37,25],[251,-38],[24,-35],[72,-45],[112,11],[56,-10],[23,-24],[-4,-44],[35,-16],[37,12],[49,1],[52,-11],[53,6],[49,-52],[34,19],[-23,37],[13,27],[88,-17],[58,4],[80,-29],[-9960,-25]],[[7918,9684],[-157,-23],[51,77],[23,7],[21,-4],[70,-33],[-8,-24]],[[6420,9816],[-37,-8],[-25,-4],[-4,-10],[-33,-10],[-30,14],[16,19],[-62,2],[54,10],[43,1],[5,-16],[16,14],[26,10],[42,-13],[-11,-9]],[[7775,9718],[-60,-8],[-78,17],[-46,23],[-21,42],[-38,12],[72,40],[60,14],[54,-30],[64,-57],[-7,-53]],[[5844,4990],[11,-33],[-1,-35],[-8,-7]],[[5821,4978],[7,-6],[16,18]],[[4526,6298],[1,25]],[[6188,6023],[-4,26],[-8,17],[-2,24],[-15,21],[-15,50],[-7,48],[-20,40],[-12,10],[-18,56],[-4,41],[2,35],[-16,66],[-13,23],[-15,12],[-10,34],[2,13],[-8,31],[-8,13],[-11,44],[-17,48],[-14,40],[-14,0],[5,33],[1,20],[3,24]],[[6344,6744],[11,-51],[14,-13],[5,-21],[18,-25],[2,-24],[-3,-20],[4,-20],[8,-16],[4,-20],[4,-14]],[[6427,6512],[5,-22]],[[6444,6180],[-80,-23],[-26,-26],[-20,-62],[-13,-10],[-7,20],[-11,-3],[-27,6],[-5,5],[-32,-1],[-7,-5],[-12,15],[-7,-29],[3,-25],[-12,-19]],[[5943,5617],[-4,1],[0,29],[-3,20],[-14,24],[-4,42],[4,44],[-13,4],[-2,-13],[-17,-3],[7,-17],[2,-36],[-15,-32],[-14,-43],[-14,-6],[-23,34],[-11,-12],[-3,-17],[-14,-11],[-1,-12],[-28,0],[-3,12],[-20,2],[-10,-10],[-8,5],[-14,34],[-5,17],[-20,-9],[-8,-27],[-7,-53],[-10,-11],[-8,-6]],[[5663,5567],[-2,2]],[[5635,5716],[0,14],[-10,17],[-1,35],[-5,23],[-10,-4],[3,22],[7,25],[-3,24],[9,18],[-6,14],[7,36],[13,44],[24,-4],[-1,234]],[[6023,6357],[9,-58],[-6,-10],[4,-61],[11,-71],[10,-14],[15,-22]],[[5943,5624],[0,-7]],[[5943,5617],[0,-46]],[[5944,5309],[-17,-28],[-20,1],[-22,-14],[-18,13],[-11,-16]],[[5682,5544],[-19,23]],[[4535,5861],[-11,46],[-14,21],[12,11],[14,41],[6,31]],[[4536,5789],[-4,45]],[[9502,4438],[8,-20],[-19,0],[-11,37],[17,-15],[5,-2]],[[9467,4474],[-11,-1],[-17,6],[-5,9],[1,23],[19,-9],[9,-12],[4,-16]],[[9490,4490],[-4,-11],[-21,52],[-5,35],[9,0],[10,-47],[11,-29]],[[9440,4565],[1,-12],[-22,25],[-15,21],[-10,20],[4,6],[13,-14],[23,-27],[6,-19]],[[9375,4623],[-5,-3],[-13,14],[-11,24],[1,10],[17,-25],[11,-20]],[[4682,5458],[-8,5],[-20,24],[-14,31],[-5,22],[-3,43]],[[2561,5848],[-3,-14],[-16,1],[-10,6],[-12,12],[-15,3],[-8,13]],[[6198,5735],[9,-11],[5,-25],[13,-24],[14,-1],[26,16],[30,7],[25,18],[13,4],[10,11],[16,2]],[[6359,5732],[0,-1],[0,-25],[0,-59],[0,-31],[-13,-36],[-19,-50]],[[6359,5732],[9,1],[13,9],[14,6],[14,20],[10,0],[1,-16],[-3,-35],[0,-31],[-6,-21],[-7,-64],[-14,-66],[-17,-75],[-24,-87],[-23,-66],[-33,-81],[-28,-48],[-42,-58],[-25,-45],[-31,-72],[-6,-31],[-6,-14]],[[3412,5410],[34,-11],[2,10],[23,4],[30,-15]],[[3489,5306],[10,-35],[-4,-25]],[[5626,7957],[-8,-15],[-5,-24]],[[5380,7746],[7,5]],[[5663,8957],[-47,-17],[-27,-41],[4,-36],[-44,-48],[-54,-50],[-20,-84],[20,-41],[26,-33],[-25,-67],[-29,-14],[-11,-99],[-15,-55],[-34,6],[-16,-47],[-32,-3],[-9,56],[-23,67],[-21,84]],[[5890,3478],[-5,-26],[-17,-6],[-16,32],[0,20],[7,22],[3,17],[8,5],[14,-11]],[[5999,7104],[-2,45],[7,25]],[[6004,7174],[7,13],[7,13],[2,33],[9,-12],[31,17],[14,-12],[23,1],[32,22],[15,-1],[32,9]],[[5051,5420],[-22,-12]],[[7849,5777],[-25,28],[-24,-2],[4,47],[-24,0],[-2,-65],[-15,-87],[-10,-52],[2,-43],[18,-2],[12,-53],[5,-52],[15,-33],[17,-7],[14,-31]],[[7779,5439],[-11,23],[-4,29],[-15,34],[-14,28],[-4,-35],[-5,33],[3,37],[8,56]],[[6883,7252],[16,60],[-6,44],[-20,14],[7,26],[23,-3],[13,33],[9,38],[37,13],[-6,-27],[4,-17],[12,2]],[[6497,7255],[-5,42],[4,62],[-22,20],[8,40],[-19,4],[6,49],[26,-14],[25,19],[-20,35],[-8,34],[-23,-15],[-3,-43],[-8,38]],[[6554,7498],[31,1],[-4,29],[24,21],[23,34],[37,-31],[3,-47],[11,-12],[30,2],[9,-10],[14,-61],[32,-41],[18,-28],[29,-29],[37,-25],[-1,-36]],[[8471,4532],[3,14],[24,13],[19,2],[9,8],[10,-8],[-10,-16],[-29,-25],[-23,-17]],[[3286,5693],[16,8],[6,-2],[-1,-44],[-23,-7],[-5,6],[8,16],[-1,23]],[[5233,7240],[31,24],[19,-7],[-1,-30],[24,22],[2,-12],[-14,-29],[0,-27],[9,-15],[-3,-51],[-19,-29],[6,-33],[14,-1],[7,-28],[11,-9]],[[6004,7174],[-11,27],[11,22],[-17,-5],[-23,13],[-19,-34],[-43,-6],[-22,31],[-30,2],[-6,-24],[-20,-7],[-26,31],[-31,-1],[-16,59],[-21,33],[14,46],[-18,28],[31,56],[43,3],[12,45],[53,-8],[33,38],[32,17],[46,1],[49,-42],[40,-22],[32,9],[24,-6],[33,31]],[[5777,7539],[3,-23],[25,-19],[-5,-14],[-33,-3],[-12,-19],[-23,-31],[-9,27],[0,12]],[[8382,6499],[-17,-95],[-12,-49],[-14,50],[-4,44],[17,58],[22,45],[13,-18],[-5,-35]],[[6088,4781],[-12,-73],[1,-33],[18,-22],[1,-15],[-8,-36],[2,-18],[-2,-28],[10,-37],[11,-58],[10,-13]],[[5909,4512],[-15,18],[-18,10],[-11,10],[-12,15]],[[5844,4990],[10,8],[31,-1],[56,4]],[[6061,7840],[-22,-5],[-18,-19],[-26,-3],[-24,-22],[1,-37],[14,-14],[28,4],[-5,-21],[-31,-11],[-37,-34],[-16,12],[6,28],[-30,17],[5,12],[26,19],[-8,14],[-43,15],[-2,22],[-25,-8],[-11,-32],[-21,-44]],[[3517,3063],[-12,-38],[-31,-32],[-21,11],[-15,-6],[-26,25],[-18,-1],[-17,32]],[[679,6185],[-4,-10],[-7,8],[1,17],[-4,21],[1,7],[5,10],[-2,11],[1,6],[3,-1],[10,-10],[5,-5],[5,-8],[7,-21],[-1,-3],[-11,-13],[-9,-9]],[[664,6277],[-9,-4],[-5,12],[-3,5],[0,4],[3,5],[9,-6],[8,-9],[-3,-7]],[[646,6309],[-1,-7],[-15,2],[2,7],[14,-2]],[[621,6317],[-2,-3],[-2,1],[-9,2],[-4,13],[-1,2],[7,8],[3,-3],[8,-20]],[[574,6356],[-4,-6],[-9,11],[1,4],[5,6],[6,-1],[1,-14]],[[3135,7724],[5,-19],[-30,-29],[-29,-20],[-29,-18],[-15,-35],[-4,-13],[-1,-31],[10,-32],[11,-1],[-3,21],[8,-13],[-2,-17],[-19,-9],[-13,1],[-20,-10],[-12,-3],[-17,-3],[-23,-17],[41,11],[8,-11],[-39,-18],[-17,0],[0,7],[-8,-16],[8,-3],[-6,-43],[-20,-45],[-2,15],[-6,3],[-9,15],[5,-32],[7,-10],[1,-23],[-9,-23],[-16,-47],[-2,3],[8,40],[-14,22],[-3,49],[-5,-25],[5,-38],[-18,10],[19,-19],[1,-57],[8,-4],[3,-20],[4,-59],[-17,-44],[-29,-18],[-18,-34],[-14,-4],[-14,-22],[-4,-20],[-31,-38],[-16,-28],[-13,-35],[-4,-42],[5,-41],[9,-51],[13,-41],[0,-26],[13,-69],[-1,-39],[-1,-23],[-7,-36],[-8,-8],[-14,7],[-4,26],[-11,14],[-15,51],[-13,45],[-4,23],[6,39],[-8,33],[-22,49],[-10,9],[-28,-27],[-5,3],[-14,28],[-17,14],[-32,-7],[-24,7],[-21,-5],[-12,-9],[5,-15],[0,-24],[5,-12],[-5,-8],[-10,9],[-11,-11],[-20,2],[-20,31],[-25,-8],[-20,14],[-17,-4],[-24,-14],[-25,-44],[-27,-25],[-16,-28],[-6,-27],[0,-41],[1,-28],[5,-20]],[[1746,6980],[-4,30],[-18,34],[-13,7],[-3,17],[-16,3],[-10,16],[-26,6],[-7,9],[-3,32],[-27,60],[-23,82],[1,14],[-13,19],[-21,50],[-4,48],[-15,32],[6,49],[-1,51],[-8,45],[10,56],[4,53],[3,54],[-5,79],[-9,51],[-8,27],[4,12],[40,-20],[15,-56],[7,15],[-5,49],[-9,48]],[[750,8432],[-28,-23],[-14,15],[-4,28],[25,21],[15,9],[18,-4],[12,-18],[-24,-28]],[[401,8597],[-18,-9],[-18,11],[-17,16],[28,10],[22,-6],[3,-22]],[[230,8826],[17,-12],[17,6],[23,-15],[27,-8],[-2,-7],[-21,-12],[-21,13],[-11,11],[-24,-4],[-7,5],[2,23]],[[1374,8295],[-15,22],[-25,19],[-8,52],[-36,47],[-15,56],[-26,4],[-44,2],[-33,17],[-57,61],[-27,11],[-49,21],[-38,-5],[-55,27],[-33,25],[-30,-12],[5,-41],[-15,-4],[-32,-12],[-25,-20],[-30,-13],[-4,35],[12,58],[30,18],[-8,15],[-35,-33],[-19,-39],[-40,-42],[20,-29],[-26,-42],[-30,-25],[-28,-18],[-7,-26],[-43,-31],[-9,-28],[-32,-25],[-20,5],[-25,-17],[-29,-20],[-23,-20],[-47,-16],[-5,9],[31,28],[27,18],[29,33],[35,6],[14,25],[38,35],[6,12],[21,21],[5,44],[14,35],[-32,-18],[-9,11],[-15,-22],[-18,30],[-8,-21],[-10,29],[-28,-23],[-17,0],[-3,35],[5,21],[-17,22],[-37,-12],[-23,28],[-19,14],[0,34],[-22,25],[11,34],[23,33],[10,30],[22,4],[19,-9],[23,28],[20,-5],[21,19],[-5,27],[-16,10],[21,23],[-17,-1],[-30,-13],[-8,-13],[-22,13],[-39,-6],[-41,14],[-12,24],[-35,34],[39,25],[62,29],[23,0],[-4,-30],[59,2],[-23,37],[-34,23],[-20,29],[-26,25],[-38,19],[15,31],[49,2],[35,27],[7,29],[28,28],[28,6],[52,27],[26,-4],[42,31],[42,-12],[21,-27],[12,11],[47,-3],[-2,-14],[43,-10],[28,6],[59,-18],[53,-6],[21,-8],[37,10],[42,-18],[31,-8]],[[3018,5753],[-1,-14],[-16,-7],[9,-26],[0,-31],[-12,-35],[10,-47],[12,4],[6,43],[-8,21],[-2,45],[35,24],[-4,27],[10,19],[10,-41],[19,-1],[18,-33],[1,-20],[25,0],[30,6],[16,-27],[21,-7],[16,18],[0,15],[34,4],[34,1],[-24,-18],[10,-28],[22,-4],[21,-29],[4,-48],[15,2],[11,-14]],[[8001,6331],[-37,-51],[-24,-56],[-6,-41],[22,-62],[25,-77],[26,-37],[17,-47],[12,-109],[-3,-104],[-24,-39],[-31,-38],[-23,-49],[-35,-55],[-10,37],[8,40],[-21,34]],[[9661,4085],[-9,-8],[-9,26],[1,16],[17,-34]],[[9641,4175],[4,-47],[-7,7],[-6,-3],[-4,16],[0,45],[13,-18]],[[6475,6041],[-21,-16],[-5,-26],[-1,-20],[-27,-25],[-45,-28],[-24,-41],[-13,-3],[-8,3],[-16,-25],[-18,-11],[-23,-3],[-7,-3],[-6,-16],[-8,-4],[-4,-15],[-14,1],[-9,-8],[-19,3],[-7,35],[1,32],[-5,17],[-5,44],[-8,24],[5,3],[-2,27],[3,12],[-1,25]],[[5817,3752],[11,0],[14,-10],[9,7],[15,-6]],[[5911,3478],[-7,-43],[-3,-49],[-7,-27],[-19,-30],[-5,-8],[-12,-30],[-8,-31],[-16,-42],[-31,-61],[-20,-36],[-21,-26],[-29,-23],[-14,-3],[-3,-17],[-17,9],[-14,-11],[-30,11],[-17,-7],[-12,3],[-28,-23],[-24,-10],[-17,-22],[-13,-1],[-11,21],[-10,1],[-12,26],[-1,-8],[-4,16],[0,34],[-9,40],[9,11],[0,45],[-19,55],[-14,50],[0,1],[-20,76]],[[5840,4141],[-21,-8],[-15,-23],[-4,-21],[-10,-4],[-24,-49],[-15,-38],[-10,-2],[-9,7],[-31,7]]],transform:{scale:[.036003600360036005,.016927109510951093],translate:[-180,-85.609038]}},l.prototype.usaTopo="__USA__",l.prototype.latLngToXY=function(a,b){return this.projection([b,a])
+},l.prototype.addLayer=function(a,b,c){var d;return d=c?this.svg.insert("g",":first-child"):this.svg.append("g"),d.attr("id",b||"").attr("class",a||"")},l.prototype.updateChoropleth=function(a){var b=this.svg;for(var c in a)if(a.hasOwnProperty(c)){var d,e=a[c];if(!c)continue;d="string"==typeof e?e:"string"==typeof e.color?e.color:this.options.fills[e.fillKey],e===Object(e)&&(this.options.data[c]=k(e,this.options.data[c]||{}),this.svg.select("."+c).attr("data-info",JSON.stringify(this.options.data[c]))),b.selectAll("."+c).transition().style("fill",d)}},l.prototype.updatePopup=function(a,b,c){var d=this;a.on("mousemove",null),a.on("mousemove",function(){var e=m.mouse(d.options.element);m.select(d.svg[0][0].parentNode).select(".datamaps-hoverover").style("top",e[1]+30+"px").html(function(){var d=JSON.parse(a.attr("data-info"));return c.popupTemplate(b,d)}).style("left",e[0]+"px")}),m.select(d.svg[0][0].parentNode).select(".datamaps-hoverover").style("display","block")},l.prototype.addPlugin=function(a,b){var c=this;"undefined"==typeof l.prototype[a]&&(l.prototype[a]=function(d,e,f,g){var h;"undefined"==typeof g&&(g=!1),"function"==typeof e&&(f=e,e=void 0),e=k(e||{},c.options[a+"Config"]),!g&&this.options[a+"Layer"]?(h=this.options[a+"Layer"],e=e||this.options[a+"Options"]):(h=this.addLayer(a),this.options[a+"Layer"]=h,this.options[a+"Options"]=e),b.apply(this,[h,d,e]),f&&f(h)})},"function"==typeof define&&define.amd?define("datamaps",function(a){return m=a("d3"),n=a("topojson"),l}):window.Datamap=window.Datamaps=l,window.jQuery&&(window.jQuery.fn.datamaps=function(a,b){a=a||{},a.element=this[0];var c=new l(a);return"function"==typeof b&&b(c,a),this})}();
diff --git a/modules/http/static/datamaps.world.min.spdx b/modules/http/static/datamaps.world.min.spdx
new file mode 100644
index 0000000..b699d72
--- /dev/null
+++ b/modules/http/static/datamaps.world.min.spdx
@@ -0,0 +1,11 @@
+SPDXVersion: SPDX-2.1
+DataLicense: CC0-1.0
+SPDXID: SPDXRef-DOCUMENT
+DocumentName: datamaps
+DocumentNamespace: http://spdx.org/spdxdocs/spdx-v2.1-b0f0e722-a8b4-4961-825d-7392f165a0be
+
+PackageName: datamaps
+PackageVersion: 0.3.7
+PackageDownloadLocation: git+https://github.com/markmarkoh/datamaps.git@37beb5995b8489906e070ec6002c7f7d84b17c7a/datamaps.world.min.js
+PackageOriginator: Person: Mark DiMarco (mark.dimarco@gmail.com)
+PackageLicenseDeclared: MIT
diff --git a/modules/http/static/dygraph.min.js b/modules/http/static/dygraph.min.js
new file mode 100644
index 0000000..613c3b8
--- /dev/null
+++ b/modules/http/static/dygraph.min.js
@@ -0,0 +1,7 @@
+/*! @license Copyright 2017 Dan Vanderkam (danvdk@gmail.com)
+ * SPDX-License-Identifier: MIT */
+!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.Dygraph=t()}}(function(){return function t(e,a,i){function n(o,s){if(!a[o]){if(!e[o]){var l="function"==typeof require&&require;if(!s&&l)return l(o,!0);if(r)return r(o,!0);var h=new Error("Cannot find module '"+o+"'");throw h.code="MODULE_NOT_FOUND",h}var u=a[o]={exports:{}};e[o][0].call(u.exports,function(t){var a=e[o][1][t];return n(a||t)},u,u.exports,t,e,a,i)}return a[o].exports}for(var r="function"==typeof require&&require,o=0;o<i.length;o++)n(i[o]);return n}({1:[function(t,e,a){function i(){throw new Error("setTimeout has not been defined")}function n(){throw new Error("clearTimeout has not been defined")}function r(t){if(d===setTimeout)return setTimeout(t,0);if((d===i||!d)&&setTimeout)return d=setTimeout,setTimeout(t,0);try{return d(t,0)}catch(e){try{return d.call(null,t,0)}catch(e){return d.call(this,t,0)}}}function o(t){if(c===clearTimeout)return clearTimeout(t);if((c===n||!c)&&clearTimeout)return c=clearTimeout,clearTimeout(t);try{return c(t)}catch(e){try{return c.call(null,t)}catch(e){return c.call(this,t)}}}function s(){_&&g&&(_=!1,g.length?f=g.concat(f):v=-1,f.length&&l())}function l(){if(!_){var t=r(s);_=!0;for(var e=f.length;e;){for(g=f,f=[];++v<e;)g&&g[v].run();v=-1,e=f.length}g=null,_=!1,o(t)}}function h(t,e){this.fun=t,this.array=e}function u(){}var d,c,p=e.exports={};!function(){try{d="function"==typeof setTimeout?setTimeout:i}catch(t){d=i}try{c="function"==typeof clearTimeout?clearTimeout:n}catch(t){c=n}}();var g,f=[],_=!1,v=-1;p.nextTick=function(t){var e=new Array(arguments.length-1);if(arguments.length>1)for(var a=1;a<arguments.length;a++)e[a-1]=arguments[a];f.push(new h(t,e)),1!==f.length||_||r(l)},h.prototype.run=function(){this.fun.apply(null,this.array)},p.title="browser",p.browser=!0,p.env={},p.argv=[],p.version="",p.versions={},p.on=u,p.addListener=u,p.once=u,p.off=u,p.removeListener=u,p.removeAllListeners=u,p.emit=u,p.prependListener=u,p.prependOnceListener=u,p.listeners=function(t){return[]},p.binding=function(t){throw new Error("process.binding is not supported")},p.cwd=function(){return"/"},p.chdir=function(t){throw new Error("process.chdir is not supported")},p.umask=function(){return 0}},{}],2:[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=t("./bars"),n=function(t){return t&&t.__esModule?t:{default:t}}(i),r=function(){};r.prototype=new n.default,r.prototype.extractSeries=function(t,e,a){for(var i,n,r,o=[],s=a.get("logscale"),l=0;l<t.length;l++)i=t[l][0],r=t[l][e],s&&null!==r&&(r[0]<=0||r[1]<=0||r[2]<=0)&&(r=null),null!==r?(n=r[1],null===n||isNaN(n)?o.push([i,n,[n,n]]):o.push([i,n,[r[0],r[2]]])):o.push([i,null,[null,null]]);return o},r.prototype.rollingAverage=function(t,e,a){e=Math.min(e,t.length);var i,n,r,o,s,l,h,u=[];for(n=0,o=0,r=0,s=0,l=0;l<t.length;l++){if(i=t[l][1],h=t[l][2],u[l]=t[l],null===i||isNaN(i)||(n+=h[0],o+=i,r+=h[1],s+=1),l-e>=0){var d=t[l-e];null===d[1]||isNaN(d[1])||(n-=d[2][0],o-=d[1],r-=d[2][1],s-=1)}u[l]=s?[t[l][0],1*o/s,[1*n/s,1*r/s]]:[t[l][0],null,[null,null]]}return u},a.default=r,e.exports=a.default},{"./bars":5}],3:[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=t("./bars"),n=function(t){return t&&t.__esModule?t:{default:t}}(i),r=function(){};r.prototype=new n.default,r.prototype.extractSeries=function(t,e,a){for(var i,n,r,o,s=[],l=a.get("sigma"),h=a.get("logscale"),u=0;u<t.length;u++)i=t[u][0],o=t[u][e],h&&null!==o&&(o[0]<=0||o[0]-l*o[1]<=0)&&(o=null),null!==o?(n=o[0],null===n||isNaN(n)?s.push([i,n,[n,n,n]]):(r=l*o[1],s.push([i,n,[n-r,n+r,o[1]]]))):s.push([i,null,[null,null,null]]);return s},r.prototype.rollingAverage=function(t,e,a){e=Math.min(e,t.length);var i,n,r,o,s,l,h,u,d,c=[],p=a.get("sigma");for(i=0;i<t.length;i++){for(s=0,u=0,l=0,n=Math.max(0,i-e+1);n<i+1;n++)null===(r=t[n][1])||isNaN(r)||(l++,s+=r,u+=Math.pow(t[n][2][2],2));l?(h=Math.sqrt(u)/l,d=s/l,c[i]=[t[i][0],d,[d-p*h,d+p*h]]):(o=1==e?t[i][1]:null,c[i]=[t[i][0],o,[o,o]])}return c},a.default=r,e.exports=a.default},{"./bars":5}],4:[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=t("./bars"),n=function(t){return t&&t.__esModule?t:{default:t}}(i),r=function(){};r.prototype=new n.default,r.prototype.extractSeries=function(t,e,a){for(var i,n,r,o,s,l,h,u,d=[],c=a.get("sigma"),p=a.get("logscale"),g=0;g<t.length;g++)i=t[g][0],r=t[g][e],p&&null!==r&&(r[0]<=0||r[1]<=0)&&(r=null),null!==r?(o=r[0],s=r[1],null===o||isNaN(o)?d.push([i,o,[o,o,o,s]]):(l=s?o/s:0,h=s?c*Math.sqrt(l*(1-l)/s):1,u=100*h,n=100*l,d.push([i,n,[n-u,n+u,o,s]]))):d.push([i,null,[null,null,null,null]]);return d},r.prototype.rollingAverage=function(t,e,a){e=Math.min(e,t.length);var i,n,r,o,s=[],l=a.get("sigma"),h=a.get("wilsonInterval"),u=0,d=0;for(r=0;r<t.length;r++){u+=t[r][2][2],d+=t[r][2][3],r-e>=0&&(u-=t[r-e][2][2],d-=t[r-e][2][3]);var c=t[r][0],p=d?u/d:0;if(h)if(d){var g=p<0?0:p,f=d,_=l*Math.sqrt(g*(1-g)/f+l*l/(4*f*f)),v=1+l*l/d;i=(g+l*l/(2*d)-_)/v,n=(g+l*l/(2*d)+_)/v,s[r]=[c,100*g,[100*i,100*n]]}else s[r]=[c,0,[0,0]];else o=d?l*Math.sqrt(p*(1-p)/d):1,s[r]=[c,100*p,[100*(p-o),100*(p+o)]]}return s},a.default=r,e.exports=a.default},{"./bars":5}],5:[function(t,e,a){"use strict";function i(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(a,"__esModule",{value:!0});var n=t("./datahandler"),r=i(n),o=t("../dygraph-layout"),s=i(o),l=function(){r.default.call(this)};l.prototype=new r.default,l.prototype.extractSeries=function(t,e,a){},l.prototype.rollingAverage=function(t,e,a){},l.prototype.onPointsCreated_=function(t,e){for(var a=0;a<t.length;++a){var i=t[a],n=e[a];n.y_top=NaN,n.y_bottom=NaN,n.yval_minus=r.default.parseFloat(i[2][0]),n.yval_plus=r.default.parseFloat(i[2][1])}},l.prototype.getExtremeYValues=function(t,e,a){for(var i,n=null,r=null,o=t.length-1,s=0;s<=o;s++)if(null!==(i=t[s][1])&&!isNaN(i)){var l=t[s][2][0],h=t[s][2][1];l>i&&(l=i),h<i&&(h=i),(null===r||h>r)&&(r=h),(null===n||l<n)&&(n=l)}return[n,r]},l.prototype.onLineEvaluated=function(t,e,a){for(var i,n=0;n<t.length;n++)i=t[n],i.y_top=s.default.calcYNormal_(e,i.yval_minus,a),i.y_bottom=s.default.calcYNormal_(e,i.yval_plus,a)},a.default=l,e.exports=a.default},{"../dygraph-layout":13,"./datahandler":6}],6:[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=function(){},n=i;n.X=0,n.Y=1,n.EXTRAS=2,n.prototype.extractSeries=function(t,e,a){},n.prototype.seriesToPoints=function(t,e,a){for(var i=[],r=0;r<t.length;++r){var o=t[r],s=o[1],l=null===s?null:n.parseFloat(s),h={x:NaN,y:NaN,xval:n.parseFloat(o[0]),yval:l,name:e,idx:r+a};i.push(h)}return this.onPointsCreated_(t,i),i},n.prototype.onPointsCreated_=function(t,e){},n.prototype.rollingAverage=function(t,e,a){},n.prototype.getExtremeYValues=function(t,e,a){},n.prototype.onLineEvaluated=function(t,e,a){},n.parseFloat=function(t){return null===t?NaN:t},a.default=i,e.exports=a.default},{}],7:[function(t,e,a){"use strict";function i(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(a,"__esModule",{value:!0});var n=t("./datahandler"),r=(i(n),t("./default")),o=i(r),s=function(){};s.prototype=new o.default,s.prototype.extractSeries=function(t,e,a){for(var i,n,r,o,s,l,h=[],u=a.get("logscale"),d=0;d<t.length;d++)i=t[d][0],r=t[d][e],u&&null!==r&&(r[0]<=0||r[1]<=0)&&(r=null),null!==r?(o=r[0],s=r[1],null===o||isNaN(o)?h.push([i,o,[o,s]]):(l=s?o/s:0,n=100*l,h.push([i,n,[o,s]]))):h.push([i,null,[null,null]]);return h},s.prototype.rollingAverage=function(t,e,a){e=Math.min(e,t.length);var i,n=[],r=0,o=0;for(i=0;i<t.length;i++){r+=t[i][2][0],o+=t[i][2][1],i-e>=0&&(r-=t[i-e][2][0],o-=t[i-e][2][1]);var s=t[i][0],l=o?r/o:0;n[i]=[s,100*l]}return n},a.default=s,e.exports=a.default},{"./datahandler":6,"./default":8}],8:[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=t("./datahandler"),n=function(t){return t&&t.__esModule?t:{default:t}}(i),r=function(){};r.prototype=new n.default,r.prototype.extractSeries=function(t,e,a){for(var i=[],n=a.get("logscale"),r=0;r<t.length;r++){var o=t[r][0],s=t[r][e];n&&s<=0&&(s=null),i.push([o,s])}return i},r.prototype.rollingAverage=function(t,e,a){e=Math.min(e,t.length);var i,n,r,o,s,l=[];if(1==e)return t;for(i=0;i<t.length;i++){for(o=0,s=0,n=Math.max(0,i-e+1);n<i+1;n++)null===(r=t[n][1])||isNaN(r)||(s++,o+=t[n][1]);l[i]=s?[t[i][0],o/s]:[t[i][0],null]}return l},r.prototype.getExtremeYValues=function(t,e,a){for(var i,n=null,r=null,o=t.length-1,s=0;s<=o;s++)null===(i=t[s][1])||isNaN(i)||((null===r||i>r)&&(r=i),(null===n||i<n)&&(n=i));return[n,r]},a.default=r,e.exports=a.default},{"./datahandler":6}],9:[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=t("./dygraph-utils"),n=function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e.default=t,e}(i),r=t("./dygraph"),o=function(t){return t&&t.__esModule?t:{default:t}}(r),s=function(t,e,a,i){if(this.dygraph_=t,this.layout=i,this.element=e,this.elementContext=a,this.height=t.height_,this.width=t.width_,!n.isCanvasSupported(this.element))throw"Canvas is not supported.";this.area=i.getPlotArea();var r=this.dygraph_.canvas_ctx_;r.beginPath(),r.rect(this.area.x,this.area.y,this.area.w,this.area.h),r.clip(),r=this.dygraph_.hidden_ctx_,r.beginPath(),r.rect(this.area.x,this.area.y,this.area.w,this.area.h),r.clip()};s.prototype.clear=function(){this.elementContext.clearRect(0,0,this.width,this.height)},s.prototype.render=function(){this._updatePoints(),this._renderLineChart()},s._getIteratorPredicate=function(t){return t?s._predicateThatSkipsEmptyPoints:null},s._predicateThatSkipsEmptyPoints=function(t,e){return null!==t[e].yval},s._drawStyledLine=function(t,e,a,i,r,o,l){var h=t.dygraph,u=h.getBooleanOption("stepPlot",t.setName);n.isArrayLike(i)||(i=null);var d=h.getBooleanOption("drawGapEdgePoints",t.setName),c=t.points,p=t.setName,g=n.createIterator(c,0,c.length,s._getIteratorPredicate(h.getBooleanOption("connectSeparatedPoints",p))),f=i&&i.length>=2,_=t.drawingContext;_.save(),f&&_.setLineDash&&_.setLineDash(i);var v=s._drawSeries(t,g,a,l,r,d,u,e);s._drawPointsOnLine(t,v,o,e,l),f&&_.setLineDash&&_.setLineDash([]),_.restore()},s._drawSeries=function(t,e,a,i,n,r,o,s){var l,h,u=null,d=null,c=null,p=[],g=!0,f=t.drawingContext;f.beginPath(),f.strokeStyle=s,f.lineWidth=a;for(var _=e.array_,v=e.end_,y=e.predicate_,x=e.start_;x<v;x++){if(h=_[x],y){for(;x<v&&!y(_,x);)x++;if(x==v)break;h=_[x]}if(null===h.canvasy||h.canvasy!=h.canvasy)o&&null!==u&&(f.moveTo(u,d),f.lineTo(h.canvasx,d)),u=d=null;else{if(l=!1,r||null===u){e.nextIdx_=x,e.next(),c=e.hasNext?e.peek.canvasy:null;var m=null===c||c!=c;l=null===u&&m,r&&(!g&&null===u||e.hasNext&&m)&&(l=!0)}null!==u?a&&(o&&(f.moveTo(u,d),f.lineTo(h.canvasx,d)),f.lineTo(h.canvasx,h.canvasy)):f.moveTo(h.canvasx,h.canvasy),(n||l)&&p.push([h.canvasx,h.canvasy,h.idx]),u=h.canvasx,d=h.canvasy}g=!1}return f.stroke(),p},s._drawPointsOnLine=function(t,e,a,i,n){for(var r=t.drawingContext,o=0;o<e.length;o++){var s=e[o];r.save(),a.call(t.dygraph,t.dygraph,t.setName,r,s[0],s[1],i,n,s[2]),r.restore()}},s.prototype._updatePoints=function(){for(var t=this.layout.points,e=t.length;e--;)for(var a=t[e],i=a.length;i--;){var n=a[i];n.canvasx=this.area.w*n.x+this.area.x,n.canvasy=this.area.h*n.y+this.area.y}},s.prototype._renderLineChart=function(t,e){var a,i,r=e||this.elementContext,o=this.layout.points,s=this.layout.setNames;this.colors=this.dygraph_.colorsMap_;var l=this.dygraph_.getOption("plotter"),h=l;n.isArrayLike(h)||(h=[h]);var u={};for(a=0;a<s.length;a++){i=s[a];var d=this.dygraph_.getOption("plotter",i);d!=l&&(u[i]=d)}for(a=0;a<h.length;a++)for(var c=h[a],p=a==h.length-1,g=0;g<o.length;g++)if(i=s[g],!t||i==t){var f=o[g],_=c;if(i in u){if(!p)continue;_=u[i]}var v=this.colors[i],y=this.dygraph_.getOption("strokeWidth",i);r.save(),r.strokeStyle=v,r.lineWidth=y,_({points:f,setName:i,drawingContext:r,color:v,strokeWidth:y,dygraph:this.dygraph_,axis:this.dygraph_.axisPropertiesForSeries(i),plotArea:this.area,seriesIndex:g,seriesCount:o.length,singleSeriesName:t,allSeriesPoints:o}),r.restore()}},s._Plotters={linePlotter:function(t){s._linePlotter(t)},fillPlotter:function(t){s._fillPlotter(t)},errorPlotter:function(t){s._errorPlotter(t)}},s._linePlotter=function(t){var e=t.dygraph,a=t.setName,i=t.strokeWidth,r=e.getNumericOption("strokeBorderWidth",a),o=e.getOption("drawPointCallback",a)||n.Circles.DEFAULT,l=e.getOption("strokePattern",a),h=e.getBooleanOption("drawPoints",a),u=e.getNumericOption("pointSize",a);r&&i&&s._drawStyledLine(t,e.getOption("strokeBorderColor",a),i+2*r,l,h,o,u),s._drawStyledLine(t,t.color,i,l,h,o,u)},s._errorPlotter=function(t){var e=t.dygraph,a=t.setName;if(e.getBooleanOption("errorBars")||e.getBooleanOption("customBars")){e.getBooleanOption("fillGraph",a)&&console.warn("Can't use fillGraph option with error bars");var i,r=t.drawingContext,o=t.color,l=e.getNumericOption("fillAlpha",a),h=e.getBooleanOption("stepPlot",a),u=t.points,d=n.createIterator(u,0,u.length,s._getIteratorPredicate(e.getBooleanOption("connectSeparatedPoints",a))),c=NaN,p=NaN,g=[-1,-1],f=n.toRGB_(o),_="rgba("+f.r+","+f.g+","+f.b+","+l+")";r.fillStyle=_,r.beginPath();for(var v=function(t){return null===t||void 0===t||isNaN(t)};d.hasNext;){var y=d.next();!h&&v(y.y)||h&&!isNaN(p)&&v(p)?c=NaN:(i=[y.y_bottom,y.y_top],h&&(p=y.y),isNaN(i[0])&&(i[0]=y.y),isNaN(i[1])&&(i[1]=y.y),i[0]=t.plotArea.h*i[0]+t.plotArea.y,i[1]=t.plotArea.h*i[1]+t.plotArea.y,isNaN(c)||(h?(r.moveTo(c,g[0]),r.lineTo(y.canvasx,g[0]),r.lineTo(y.canvasx,g[1])):(r.moveTo(c,g[0]),r.lineTo(y.canvasx,i[0]),r.lineTo(y.canvasx,i[1])),r.lineTo(c,g[1]),r.closePath()),g=i,c=y.canvasx)}r.fill()}},s._fastCanvasProxy=function(t){var e=[],a=null,i=null,n=0,r=function(t){if(!(e.length<=1)){for(var a=e.length-1;a>0;a--){var i=e[a];if(2==i[0]){var n=e[a-1];n[1]==i[1]&&n[2]==i[2]&&e.splice(a,1)}}for(var a=0;a<e.length-1;){var i=e[a];2==i[0]&&2==e[a+1][0]?e.splice(a,1):a++}if(e.length>2&&!t){var r=0;2==e[0][0]&&r++;for(var o=null,s=null,a=r;a<e.length;a++){var i=e[a];if(1==i[0])if(null===o&&null===s)o=a,s=a;else{var l=i[2];l<e[o][2]?o=a:l>e[s][2]&&(s=a)}}var h=e[o],u=e[s];e.splice(r,e.length-r),o<s?(e.push(h),e.push(u)):o>s?(e.push(u),e.push(h)):e.push(h)}}},o=function(a){r(a);for(var o=0,s=e.length;o<s;o++){var l=e[o];1==l[0]?t.lineTo(l[1],l[2]):2==l[0]&&t.moveTo(l[1],l[2])}e.length&&(i=e[e.length-1][1]),n+=e.length,e=[]},s=function(t,n,r){var s=Math.round(n);if(null===a||s!=a){var l=a-i>1,h=s-a>1;o(l||h),a=s}e.push([t,n,r])};return{moveTo:function(t,e){s(2,t,e)},lineTo:function(t,e){s(1,t,e)},stroke:function(){o(!0),t.stroke()},fill:function(){o(!0),t.fill()},beginPath:function(){o(!0),t.beginPath()},closePath:function(){o(!0),t.closePath()},_count:function(){return n}}},s._fillPlotter=function(t){if(!t.singleSeriesName&&0===t.seriesIndex){for(var e=t.dygraph,a=e.getLabels().slice(1),i=a.length;i>=0;i--)e.visibility()[i]||a.splice(i,1);if(function(){for(var t=0;t<a.length;t++)if(e.getBooleanOption("fillGraph",a[t]))return!0;return!1}())for(var r,l,h=t.plotArea,u=t.allSeriesPoints,d=u.length,c=e.getBooleanOption("stackedGraph"),p=e.getColors(),g={},f=function(t,e,a,i){if(t.lineTo(e,a),c)for(var n=i.length-1;n>=0;n--){var r=i[n];t.lineTo(r[0],r[1])}},_=d-1;_>=0;_--){var v=t.drawingContext,y=a[_];if(e.getBooleanOption("fillGraph",y)){var x=e.getNumericOption("fillAlpha",y),m=e.getBooleanOption("stepPlot",y),b=p[_],w=e.axisPropertiesForSeries(y),A=1+w.minyval*w.yscale;A<0?A=0:A>1&&(A=1),A=h.h*A+h.y;var O,D=u[_],E=n.createIterator(D,0,D.length,s._getIteratorPredicate(e.getBooleanOption("connectSeparatedPoints",y))),L=NaN,T=[-1,-1],S=n.toRGB_(b),P="rgba("+S.r+","+S.g+","+S.b+","+x+")";v.fillStyle=P,v.beginPath();var C,M=!0;(D.length>2*e.width_||o.default.FORCE_FAST_PROXY)&&(v=s._fastCanvasProxy(v));for(var N,F=[];E.hasNext;)if(N=E.next(),n.isOK(N.y)||m){if(c){if(!M&&C==N.xval)continue;M=!1,C=N.xval,r=g[N.canvasx];var k;k=void 0===r?A:l?r[0]:r,O=[N.canvasy,k],m?-1===T[0]?g[N.canvasx]=[N.canvasy,A]:g[N.canvasx]=[N.canvasy,T[0]]:g[N.canvasx]=N.canvasy}else O=isNaN(N.canvasy)&&m?[h.y+h.h,A]:[N.canvasy,A];isNaN(L)?(v.moveTo(N.canvasx,O[1]),v.lineTo(N.canvasx,O[0])):(m?(v.lineTo(N.canvasx,T[0]),v.lineTo(N.canvasx,O[0])):v.lineTo(N.canvasx,O[0]),c&&(F.push([L,T[1]]),l&&r?F.push([N.canvasx,r[1]]):F.push([N.canvasx,O[1]]))),T=O,L=N.canvasx}else f(v,L,T[1],F),F=[],L=NaN,null===N.y_stacked||isNaN(N.y_stacked)||(g[N.canvasx]=h.h*N.y_stacked+h.y);l=m,O&&N&&(f(v,N.canvasx,O[1],F),F=[]),v.fill()}}}},a.default=s,e.exports=a.default},{"./dygraph":18,"./dygraph-utils":17}],10:[function(t,e,a){"use strict";function i(t){return t&&t.__esModule?t:{default:t}}function n(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e.default=t,e}Object.defineProperty(a,"__esModule",{value:!0});var r=t("./dygraph-tickers"),o=n(r),s=t("./dygraph-interaction-model"),l=i(s),h=t("./dygraph-canvas"),u=i(h),d=t("./dygraph-utils"),c=n(d),p={highlightCircleSize:3,highlightSeriesOpts:null,highlightSeriesBackgroundAlpha:.5,highlightSeriesBackgroundColor:"rgb(255, 255, 255)",labelsSeparateLines:!1,labelsShowZeroValues:!0,labelsKMB:!1,labelsKMG2:!1,showLabelsOnHighlight:!0,digitsAfterDecimal:2,maxNumberWidth:6,sigFigs:null,strokeWidth:1,strokeBorderWidth:0,strokeBorderColor:"white",axisTickSize:3,axisLabelFontSize:14,rightGap:5,showRoller:!1,xValueParser:void 0,delimiter:",",sigma:2,errorBars:!1,fractions:!1,wilsonInterval:!0,customBars:!1,fillGraph:!1,fillAlpha:.15,connectSeparatedPoints:!1,stackedGraph:!1,stackedGraphNaNFill:"all",hideOverlayOnMouseOut:!0,legend:"onmouseover",stepPlot:!1,xRangePad:0,yRangePad:null,drawAxesAtZero:!1,titleHeight:28,xLabelHeight:18,yLabelWidth:18,axisLineColor:"black",axisLineWidth:.3,gridLineWidth:.3,axisLabelWidth:50,gridLineColor:"rgb(128,128,128)",interactionModel:l.default.defaultModel,animatedZooms:!1,showRangeSelector:!1,rangeSelectorHeight:40,rangeSelectorPlotStrokeColor:"#808FAB",rangeSelectorPlotFillGradientColor:"white",rangeSelectorPlotFillColor:"#A7B1C4",rangeSelectorBackgroundStrokeColor:"gray",rangeSelectorBackgroundLineWidth:1,rangeSelectorPlotLineWidth:1.5,rangeSelectorForegroundStrokeColor:"black",rangeSelectorForegroundLineWidth:1,rangeSelectorAlpha:.6,showInRangeSelector:null,plotter:[u.default._fillPlotter,u.default._errorPlotter,u.default._linePlotter],plugins:[],axes:{x:{pixelsPerLabel:70,axisLabelWidth:60,axisLabelFormatter:c.dateAxisLabelFormatter,valueFormatter:c.dateValueFormatter,drawGrid:!0,drawAxis:!0,independentTicks:!0,ticker:o.dateTicker},y:{axisLabelWidth:50,pixelsPerLabel:30,valueFormatter:c.numberValueFormatter,axisLabelFormatter:c.numberAxisLabelFormatter,drawGrid:!0,drawAxis:!0,independentTicks:!0,ticker:o.numericTicks},y2:{axisLabelWidth:50,pixelsPerLabel:30,valueFormatter:c.numberValueFormatter,axisLabelFormatter:c.numberAxisLabelFormatter,drawAxis:!0,drawGrid:!1,independentTicks:!1,ticker:o.numericTicks}}};a.default=p,e.exports=a.default},{"./dygraph-canvas":9,"./dygraph-interaction-model":12,"./dygraph-tickers":16,"./dygraph-utils":17}],11:[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=t("./dygraph"),n=function(t){return t&&t.__esModule?t:{default:t}}(i),r=function(t){this.container=t};r.prototype.draw=function(t,e){this.container.innerHTML="",void 0!==this.date_graph&&this.date_graph.destroy(),this.date_graph=new n.default(this.container,t,e)},r.prototype.setSelection=function(t){var e=!1;t.length&&(e=t[0].row),this.date_graph.setSelection(e)},r.prototype.getSelection=function(){var t=[],e=this.date_graph.getSelection();if(e<0)return t;for(var a=this.date_graph.layout_.points,i=0;i<a.length;++i)t.push({row:e,column:i+1});return t},a.default=r,e.exports=a.default},{"./dygraph":18}],12:[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=t("./dygraph-utils"),n=function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e.default=t,e}(i),r={};r.maybeTreatMouseOpAsClick=function(t,e,a){a.dragEndX=n.dragGetX_(t,a),a.dragEndY=n.dragGetY_(t,a);var i=Math.abs(a.dragEndX-a.dragStartX),o=Math.abs(a.dragEndY-a.dragStartY);i<2&&o<2&&void 0!==e.lastx_&&-1!=e.lastx_&&r.treatMouseOpAsClick(e,t,a),a.regionWidth=i,a.regionHeight=o},r.startPan=function(t,e,a){var i,r;a.isPanning=!0;var o=e.xAxisRange();if(e.getOptionForAxis("logscale","x")?(a.initialLeftmostDate=n.log10(o[0]),a.dateRange=n.log10(o[1])-n.log10(o[0])):(a.initialLeftmostDate=o[0],a.dateRange=o[1]-o[0]),a.xUnitsPerPixel=a.dateRange/(e.plotter_.area.w-1),e.getNumericOption("panEdgeFraction")){var s=e.width_*e.getNumericOption("panEdgeFraction"),l=e.xAxisExtremes(),h=e.toDomXCoord(l[0])-s,u=e.toDomXCoord(l[1])+s,d=e.toDataXCoord(h),c=e.toDataXCoord(u);a.boundedDates=[d,c];var p=[],g=e.height_*e.getNumericOption("panEdgeFraction");for(i=0;i<e.axes_.length;i++){r=e.axes_[i];var f=r.extremeRange,_=e.toDomYCoord(f[0],i)+g,v=e.toDomYCoord(f[1],i)-g,y=e.toDataYCoord(_,i),x=e.toDataYCoord(v,i);p[i]=[y,x]}a.boundedValues=p}for(a.is2DPan=!1,a.axes=[],i=0;i<e.axes_.length;i++){r=e.axes_[i];var m={},b=e.yAxisRange(i);e.attributes_.getForAxis("logscale",i)?(m.initialTopValue=n.log10(b[1]),m.dragValueRange=n.log10(b[1])-n.log10(b[0])):(m.initialTopValue=b[1],m.dragValueRange=b[1]-b[0]),m.unitsPerPixel=m.dragValueRange/(e.plotter_.area.h-1),a.axes.push(m),r.valueRange&&(a.is2DPan=!0)}},r.movePan=function(t,e,a){a.dragEndX=n.dragGetX_(t,a),a.dragEndY=n.dragGetY_(t,a);var i=a.initialLeftmostDate-(a.dragEndX-a.dragStartX)*a.xUnitsPerPixel;a.boundedDates&&(i=Math.max(i,a.boundedDates[0]));var r=i+a.dateRange;if(a.boundedDates&&r>a.boundedDates[1]&&(i-=r-a.boundedDates[1],r=i+a.dateRange),e.getOptionForAxis("logscale","x")?e.dateWindow_=[Math.pow(n.LOG_SCALE,i),Math.pow(n.LOG_SCALE,r)]:e.dateWindow_=[i,r],a.is2DPan)for(var o=a.dragEndY-a.dragStartY,s=0;s<e.axes_.length;s++){var l=e.axes_[s],h=a.axes[s],u=o*h.unitsPerPixel,d=a.boundedValues?a.boundedValues[s]:null,c=h.initialTopValue+u;d&&(c=Math.min(c,d[1]));var p=c-h.dragValueRange;d&&p<d[0]&&(c-=p-d[0],p=c-h.dragValueRange),e.attributes_.getForAxis("logscale",s)?l.valueRange=[Math.pow(n.LOG_SCALE,p),Math.pow(n.LOG_SCALE,c)]:l.valueRange=[p,c]}e.drawGraph_(!1)},r.endPan=r.maybeTreatMouseOpAsClick,r.startZoom=function(t,e,a){a.isZooming=!0,a.zoomMoved=!1},r.moveZoom=function(t,e,a){a.zoomMoved=!0,a.dragEndX=n.dragGetX_(t,a),a.dragEndY=n.dragGetY_(t,a);var i=Math.abs(a.dragStartX-a.dragEndX),r=Math.abs(a.dragStartY-a.dragEndY);a.dragDirection=i<r/2?n.VERTICAL:n.HORIZONTAL,e.drawZoomRect_(a.dragDirection,a.dragStartX,a.dragEndX,a.dragStartY,a.dragEndY,a.prevDragDirection,a.prevEndX,a.prevEndY),a.prevEndX=a.dragEndX,a.prevEndY=a.dragEndY,a.prevDragDirection=a.dragDirection},r.treatMouseOpAsClick=function(t,e,a){for(var i=t.getFunctionOption("clickCallback"),n=t.getFunctionOption("pointClickCallback"),r=null,o=-1,s=Number.MAX_VALUE,l=0;l<t.selPoints_.length;l++){var h=t.selPoints_[l],u=Math.pow(h.canvasx-a.dragEndX,2)+Math.pow(h.canvasy-a.dragEndY,2);!isNaN(u)&&(-1==o||u<s)&&(s=u,o=l)}var d=t.getNumericOption("highlightCircleSize")+2;if(s<=d*d&&(r=t.selPoints_[o]),r){var c={cancelable:!0,point:r,canvasx:a.dragEndX,canvasy:a.dragEndY};if(t.cascadeEvents_("pointClick",c))return;n&&n.call(t,e,r)}var c={cancelable:!0,xval:t.lastx_,pts:t.selPoints_,canvasx:a.dragEndX,canvasy:a.dragEndY};t.cascadeEvents_("click",c)||i&&i.call(t,e,t.lastx_,t.selPoints_)},r.endZoom=function(t,e,a){e.clearZoomRect_(),a.isZooming=!1,r.maybeTreatMouseOpAsClick(t,e,a);var i=e.getArea();if(a.regionWidth>=10&&a.dragDirection==n.HORIZONTAL){var o=Math.min(a.dragStartX,a.dragEndX),s=Math.max(a.dragStartX,a.dragEndX);o=Math.max(o,i.x),s=Math.min(s,i.x+i.w),o<s&&e.doZoomX_(o,s),a.cancelNextDblclick=!0}else if(a.regionHeight>=10&&a.dragDirection==n.VERTICAL){var l=Math.min(a.dragStartY,a.dragEndY),h=Math.max(a.dragStartY,a.dragEndY);l=Math.max(l,i.y),h=Math.min(h,i.y+i.h),l<h&&e.doZoomY_(l,h),a.cancelNextDblclick=!0}a.dragStartX=null,a.dragStartY=null},r.startTouch=function(t,e,a){t.preventDefault(),t.touches.length>1&&(a.startTimeForDoubleTapMs=null);for(var i=[],n=0;n<t.touches.length;n++){var r=t.touches[n];i.push({pageX:r.pageX,pageY:r.pageY,dataX:e.toDataXCoord(r.pageX),dataY:e.toDataYCoord(r.pageY)})}if(a.initialTouches=i,1==i.length)a.initialPinchCenter=i[0],a.touchDirections={x:!0,y:!0};else if(i.length>=2){a.initialPinchCenter={pageX:.5*(i[0].pageX+i[1].pageX),pageY:.5*(i[0].pageY+i[1].pageY),dataX:.5*(i[0].dataX+i[1].dataX),dataY:.5*(i[0].dataY+i[1].dataY)};var o=180/Math.PI*Math.atan2(a.initialPinchCenter.pageY-i[0].pageY,i[0].pageX-a.initialPinchCenter.pageX);o=Math.abs(o),o>90&&(o=90-o),a.touchDirections={x:o<67.5,y:o>22.5}}a.initialRange={x:e.xAxisRange(),y:e.yAxisRange()}},r.moveTouch=function(t,e,a){a.startTimeForDoubleTapMs=null;var i,n=[];for(i=0;i<t.touches.length;i++){var r=t.touches[i];n.push({pageX:r.pageX,pageY:r.pageY})}var o,s=a.initialTouches,l=a.initialPinchCenter;o=1==n.length?n[0]:{pageX:.5*(n[0].pageX+n[1].pageX),pageY:.5*(n[0].pageY+n[1].pageY)};var h={pageX:o.pageX-l.pageX,pageY:o.pageY-l.pageY},u=a.initialRange.x[1]-a.initialRange.x[0],d=a.initialRange.y[0]-a.initialRange.y[1];h.dataX=h.pageX/e.plotter_.area.w*u,h.dataY=h.pageY/e.plotter_.area.h*d;var c,p;if(1==n.length)c=1,p=1;else if(n.length>=2){var g=s[1].pageX-l.pageX;c=(n[1].pageX-o.pageX)/g;var f=s[1].pageY-l.pageY;p=(n[1].pageY-o.pageY)/f}c=Math.min(8,Math.max(.125,c)),p=Math.min(8,Math.max(.125,p));var _=!1;if(a.touchDirections.x&&(e.dateWindow_=[l.dataX-h.dataX+(a.initialRange.x[0]-l.dataX)/c,l.dataX-h.dataX+(a.initialRange.x[1]-l.dataX)/c],_=!0),a.touchDirections.y)for(i=0;i<1;i++){var v=e.axes_[i],y=e.attributes_.getForAxis("logscale",i);y||(v.valueRange=[l.dataY-h.dataY+(a.initialRange.y[0]-l.dataY)/p,l.dataY-h.dataY+(a.initialRange.y[1]-l.dataY)/p],_=!0)}if(e.drawGraph_(!1),_&&n.length>1&&e.getFunctionOption("zoomCallback")){var x=e.xAxisRange();e.getFunctionOption("zoomCallback").call(e,x[0],x[1],e.yAxisRanges())}},r.endTouch=function(t,e,a){if(0!==t.touches.length)r.startTouch(t,e,a);else if(1==t.changedTouches.length){var i=(new Date).getTime(),n=t.changedTouches[0];a.startTimeForDoubleTapMs&&i-a.startTimeForDoubleTapMs<500&&a.doubleTapX&&Math.abs(a.doubleTapX-n.screenX)<50&&a.doubleTapY&&Math.abs(a.doubleTapY-n.screenY)<50?e.resetZoom():(a.startTimeForDoubleTapMs=i,a.doubleTapX=n.screenX,a.doubleTapY=n.screenY)}};var o=function(t,e,a){return t<e?e-t:t>a?t-a:0},s=function(t,e){var a=n.findPos(e.canvas_),i={left:a.x,right:a.x+e.canvas_.offsetWidth,top:a.y,bottom:a.y+e.canvas_.offsetHeight},r={x:n.pageX(t),y:n.pageY(t)},s=o(r.x,i.left,i.right),l=o(r.y,i.top,i.bottom);return Math.max(s,l)};r.defaultModel={mousedown:function(t,e,a){if(!t.button||2!=t.button){a.initializeMouseDown(t,e,a),t.altKey||t.shiftKey?r.startPan(t,e,a):r.startZoom(t,e,a);var i=function(t){if(a.isZooming){s(t,e)<100?r.moveZoom(t,e,a):null!==a.dragEndX&&(a.dragEndX=null,a.dragEndY=null,e.clearZoomRect_())}else a.isPanning&&r.movePan(t,e,a)},o=function t(o){a.isZooming?null!==a.dragEndX?r.endZoom(o,e,a):r.maybeTreatMouseOpAsClick(o,e,a):a.isPanning&&r.endPan(o,e,a),n.removeEvent(document,"mousemove",i),n.removeEvent(document,"mouseup",t),a.destroy()};e.addAndTrackEvent(document,"mousemove",i),e.addAndTrackEvent(document,"mouseup",o)}},willDestroyContextMyself:!0,touchstart:function(t,e,a){r.startTouch(t,e,a)},touchmove:function(t,e,a){r.moveTouch(t,e,a)},touchend:function(t,e,a){r.endTouch(t,e,a)},dblclick:function(t,e,a){if(a.cancelNextDblclick)return void(a.cancelNextDblclick=!1);var i={canvasx:a.dragEndX,canvasy:a.dragEndY,cancelable:!0};e.cascadeEvents_("dblclick",i)||t.altKey||t.shiftKey||e.resetZoom()}},r.nonInteractiveModel_={mousedown:function(t,e,a){a.initializeMouseDown(t,e,a)},mouseup:r.maybeTreatMouseOpAsClick},r.dragIsPanInteractionModel={mousedown:function(t,e,a){a.initializeMouseDown(t,e,a),r.startPan(t,e,a)},mousemove:function(t,e,a){a.isPanning&&r.movePan(t,e,a)},mouseup:function(t,e,a){a.isPanning&&r.endPan(t,e,a)}},a.default=r,e.exports=a.default},{"./dygraph-utils":17}],13:[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=t("./dygraph-utils"),n=function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e.default=t,e}(i),r=function(t){this.dygraph_=t,this.points=[],this.setNames=[],this.annotations=[],this.yAxes_=null,this.xTicks_=null,this.yTicks_=null};r.prototype.addDataset=function(t,e){this.points.push(e),this.setNames.push(t)},r.prototype.getPlotArea=function(){return this.area_},r.prototype.computePlotArea=function(){var t={x:0,y:0};t.w=this.dygraph_.width_-t.x-this.dygraph_.getOption("rightGap"),t.h=this.dygraph_.height_;var e={chart_div:this.dygraph_.graphDiv,reserveSpaceLeft:function(e){var a={x:t.x,y:t.y,w:e,h:t.h};return t.x+=e,t.w-=e,a},reserveSpaceRight:function(e){var a={x:t.x+t.w-e,y:t.y,w:e,h:t.h};return t.w-=e,a},reserveSpaceTop:function(e){var a={x:t.x,y:t.y,w:t.w,h:e};return t.y+=e,t.h-=e,a},reserveSpaceBottom:function(e){var a={x:t.x,y:t.y+t.h-e,w:t.w,h:e};return t.h-=e,a},chartRect:function(){return{x:t.x,y:t.y,w:t.w,h:t.h}}};this.dygraph_.cascadeEvents_("layout",e),this.area_=t},r.prototype.setAnnotations=function(t){this.annotations=[];for(var e=this.dygraph_.getOption("xValueParser")||function(t){return t},a=0;a<t.length;a++){var i={};if(!t[a].xval&&void 0===t[a].x)return void console.error("Annotations must have an 'x' property");if(t[a].icon&&(!t[a].hasOwnProperty("width")||!t[a].hasOwnProperty("height")))return void console.error("Must set width and height when setting annotation.icon property");n.update(i,t[a]),i.xval||(i.xval=e(i.x)),this.annotations.push(i)}},r.prototype.setXTicks=function(t){this.xTicks_=t},r.prototype.setYAxes=function(t){this.yAxes_=t},r.prototype.evaluate=function(){this._xAxis={},this._evaluateLimits(),this._evaluateLineCharts(),this._evaluateLineTicks(),this._evaluateAnnotations()},r.prototype._evaluateLimits=function(){var t=this.dygraph_.xAxisRange();this._xAxis.minval=t[0],this._xAxis.maxval=t[1];var e=t[1]-t[0];this._xAxis.scale=0!==e?1/e:1,this.dygraph_.getOptionForAxis("logscale","x")&&(this._xAxis.xlogrange=n.log10(this._xAxis.maxval)-n.log10(this._xAxis.minval),this._xAxis.xlogscale=0!==this._xAxis.xlogrange?1/this._xAxis.xlogrange:1);for(var a=0;a<this.yAxes_.length;a++){var i=this.yAxes_[a];i.minyval=i.computedValueRange[0],i.maxyval=i.computedValueRange[1],i.yrange=i.maxyval-i.minyval,i.yscale=0!==i.yrange?1/i.yrange:1,this.dygraph_.getOption("logscale")&&(i.ylogrange=n.log10(i.maxyval)-n.log10(i.minyval),i.ylogscale=0!==i.ylogrange?1/i.ylogrange:1,isFinite(i.ylogrange)&&!isNaN(i.ylogrange)||console.error("axis "+a+" of graph at "+i.g+" can't be displayed in log scale for range ["+i.minyval+" - "+i.maxyval+"]"))}},r.calcXNormal_=function(t,e,a){return a?(n.log10(t)-n.log10(e.minval))*e.xlogscale:(t-e.minval)*e.scale},r.calcYNormal_=function(t,e,a){if(a){var i=1-(n.log10(e)-n.log10(t.minyval))*t.ylogscale;return isFinite(i)?i:NaN}return 1-(e-t.minyval)*t.yscale},r.prototype._evaluateLineCharts=function(){for(var t=this.dygraph_.getOption("stackedGraph"),e=this.dygraph_.getOptionForAxis("logscale","x"),a=0;a<this.points.length;a++){for(var i=this.points[a],n=this.setNames[a],o=this.dygraph_.getOption("connectSeparatedPoints",n),s=this.dygraph_.axisPropertiesForSeries(n),l=this.dygraph_.attributes_.getForSeries("logscale",n),h=0;h<i.length;h++){var u=i[h];u.x=r.calcXNormal_(u.xval,this._xAxis,e);var d=u.yval;t&&(u.y_stacked=r.calcYNormal_(s,u.yval_stacked,l),
+null===d||isNaN(d)||(d=u.yval_stacked)),null===d&&(d=NaN,o||(u.yval=NaN)),u.y=r.calcYNormal_(s,d,l)}this.dygraph_.dataHandler_.onLineEvaluated(i,s,l)}},r.prototype._evaluateLineTicks=function(){var t,e,a,i,n,r;for(this.xticks=[],t=0;t<this.xTicks_.length;t++)e=this.xTicks_[t],a=e.label,r=!("label_v"in e),n=r?e.v:e.label_v,(i=this.dygraph_.toPercentXCoord(n))>=0&&i<1&&this.xticks.push({pos:i,label:a,has_tick:r});for(this.yticks=[],t=0;t<this.yAxes_.length;t++)for(var o=this.yAxes_[t],s=0;s<o.ticks.length;s++)e=o.ticks[s],a=e.label,r=!("label_v"in e),n=r?e.v:e.label_v,(i=this.dygraph_.toPercentYCoord(n,t))>0&&i<=1&&this.yticks.push({axis:t,pos:i,label:a,has_tick:r})},r.prototype._evaluateAnnotations=function(){var t,e={};for(t=0;t<this.annotations.length;t++){var a=this.annotations[t];e[a.xval+","+a.series]=a}if(this.annotated_points=[],this.annotations&&this.annotations.length)for(var i=0;i<this.points.length;i++){var n=this.points[i];for(t=0;t<n.length;t++){var r=n[t],o=r.xval+","+r.name;o in e&&(r.annotation=e[o],this.annotated_points.push(r))}}},r.prototype.removeAllDatasets=function(){delete this.points,delete this.setNames,delete this.setPointsLengths,delete this.setPointsOffsets,this.points=[],this.setNames=[],this.setPointsLengths=[],this.setPointsOffsets=[]},a.default=r,e.exports=a.default},{"./dygraph-utils":17}],14:[function(t,e,a){(function(t){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=null;if(void 0!==t);a.default=i,e.exports=a.default}).call(this,t("_process"))},{_process:1}],15:[function(t,e,a){(function(i){"use strict";function n(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(a,"__esModule",{value:!0});var r=t("./dygraph-utils"),o=function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e.default=t,e}(r),s=t("./dygraph-default-attrs"),l=n(s),h=t("./dygraph-options-reference"),u=(n(h),function(t){this.dygraph_=t,this.yAxes_=[],this.xAxis_={},this.series_={},this.global_=this.dygraph_.attrs_,this.user_=this.dygraph_.user_attrs_||{},this.labels_=[],this.highlightSeries_=this.get("highlightSeriesOpts")||{},this.reparseSeries()});if(u.AXIS_STRING_MAPPINGS_={y:0,Y:0,y1:0,Y1:0,y2:1,Y2:1},u.axisToIndex_=function(t){if("string"==typeof t){if(u.AXIS_STRING_MAPPINGS_.hasOwnProperty(t))return u.AXIS_STRING_MAPPINGS_[t];throw"Unknown axis : "+t}if("number"==typeof t){if(0===t||1===t)return t;throw"Dygraphs only supports two y-axes, indexed from 0-1."}if(t)throw"Unknown axis : "+t;return 0},u.prototype.reparseSeries=function(){var t=this.get("labels");if(t){this.labels_=t.slice(1),this.yAxes_=[{series:[],options:{}}],this.xAxis_={options:{}},this.series_={};for(var e=this.user_.series||{},a=0;a<this.labels_.length;a++){var i=this.labels_[a],n=e[i]||{},r=u.axisToIndex_(n.axis);this.series_[i]={idx:a,yAxis:r,options:n},this.yAxes_[r]?this.yAxes_[r].series.push(i):this.yAxes_[r]={series:[i],options:{}}}var s=this.user_.axes||{};o.update(this.yAxes_[0].options,s.y||{}),this.yAxes_.length>1&&o.update(this.yAxes_[1].options,s.y2||{}),o.update(this.xAxis_.options,s.x||{})}},u.prototype.get=function(t){var e=this.getGlobalUser_(t);return null!==e?e:this.getGlobalDefault_(t)},u.prototype.getGlobalUser_=function(t){return this.user_.hasOwnProperty(t)?this.user_[t]:null},u.prototype.getGlobalDefault_=function(t){return this.global_.hasOwnProperty(t)?this.global_[t]:l.default.hasOwnProperty(t)?l.default[t]:null},u.prototype.getForAxis=function(t,e){var a,i;if("number"==typeof e)a=e,i=0===a?"y":"y2";else{if("y1"==e&&(e="y"),"y"==e)a=0;else if("y2"==e)a=1;else{if("x"!=e)throw"Unknown axis "+e;a=-1}i=e}var n=-1==a?this.xAxis_:this.yAxes_[a];if(n){var r=n.options;if(r.hasOwnProperty(t))return r[t]}if("x"!==e||"logscale"!==t){var o=this.getGlobalUser_(t);if(null!==o)return o}var s=l.default.axes[i];return s.hasOwnProperty(t)?s[t]:this.getGlobalDefault_(t)},u.prototype.getForSeries=function(t,e){if(e===this.dygraph_.getHighlightSeries()&&this.highlightSeries_.hasOwnProperty(t))return this.highlightSeries_[t];if(!this.series_.hasOwnProperty(e))throw"Unknown series: "+e;var a=this.series_[e],i=a.options;return i.hasOwnProperty(t)?i[t]:this.getForAxis(t,a.yAxis)},u.prototype.numAxes=function(){return this.yAxes_.length},u.prototype.axisForSeries=function(t){return this.series_[t].yAxis},u.prototype.axisOptions=function(t){return this.yAxes_[t].options},u.prototype.seriesForAxis=function(t){return this.yAxes_[t].series},u.prototype.seriesNames=function(){return this.labels_},void 0!==i);a.default=u,e.exports=a.default}).call(this,t("_process"))},{"./dygraph-default-attrs":10,"./dygraph-options-reference":14,"./dygraph-utils":17,_process:1}],16:[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=t("./dygraph-utils"),n=function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e.default=t,e}(i),r=function(t,e,a,i,n,r){return o(t,e,a,function(t){return"logscale"!==t&&i(t)},n,r)};a.numericLinearTicks=r;var o=function(t,e,a,i,r,o){var s,l,h,u,c=i("pixelsPerLabel"),p=[];if(o)for(s=0;s<o.length;s++)p.push({v:o[s]});else{if(i("logscale")){u=Math.floor(a/c);var g=n.binarySearch(t,d,1),f=n.binarySearch(e,d,-1);-1==g&&(g=0),-1==f&&(f=d.length-1);var _=null;if(f-g>=u/4){for(var v=f;v>=g;v--){var y=d[v],x=Math.log(y/t)/Math.log(e/t)*a,m={v:y};null===_?_={tickValue:y,pixel_coord:x}:Math.abs(x-_.pixel_coord)>=c?_={tickValue:y,pixel_coord:x}:m.label="",p.push(m)}p.reverse()}}if(0===p.length){var b,w,A=i("labelsKMG2");A?(b=[1,2,4,8,16,32,64,128,256],w=16):(b=[1,2,5,10,20,50,100],w=10);var O,D,E,L=Math.ceil(a/c),T=Math.abs(e-t)/L,S=Math.floor(Math.log(T)/Math.log(w)),P=Math.pow(w,S);for(l=0;l<b.length&&(O=P*b[l],D=Math.floor(t/O)*O,E=Math.ceil(e/O)*O,u=Math.abs(E-D)/O,!(a/u>c));l++);for(D>E&&(O*=-1),s=0;s<=u;s++)h=D+s*O,p.push({v:h})}}var C=i("axisLabelFormatter");for(s=0;s<p.length;s++)void 0===p[s].label&&(p[s].label=C.call(r,p[s].v,0,i,r));return p};a.numericTicks=o;var s=function(t,e,a,i,n,r){var o=c(t,e,a,i);return o>=0?g(t,e,o,i,n):[]};a.dateTicker=s;var l={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};a.Granularity=l;var h={DATEFIELD_Y:0,DATEFIELD_M:1,DATEFIELD_D:2,DATEFIELD_HH:3,DATEFIELD_MM:4,DATEFIELD_SS:5,DATEFIELD_MS:6,NUM_DATEFIELDS:7},u=[];u[l.MILLISECONDLY]={datefield:h.DATEFIELD_MS,step:1,spacing:1},u[l.TWO_MILLISECONDLY]={datefield:h.DATEFIELD_MS,step:2,spacing:2},u[l.FIVE_MILLISECONDLY]={datefield:h.DATEFIELD_MS,step:5,spacing:5},u[l.TEN_MILLISECONDLY]={datefield:h.DATEFIELD_MS,step:10,spacing:10},u[l.FIFTY_MILLISECONDLY]={datefield:h.DATEFIELD_MS,step:50,spacing:50},u[l.HUNDRED_MILLISECONDLY]={datefield:h.DATEFIELD_MS,step:100,spacing:100},u[l.FIVE_HUNDRED_MILLISECONDLY]={datefield:h.DATEFIELD_MS,step:500,spacing:500},u[l.SECONDLY]={datefield:h.DATEFIELD_SS,step:1,spacing:1e3},u[l.TWO_SECONDLY]={datefield:h.DATEFIELD_SS,step:2,spacing:2e3},u[l.FIVE_SECONDLY]={datefield:h.DATEFIELD_SS,step:5,spacing:5e3},u[l.TEN_SECONDLY]={datefield:h.DATEFIELD_SS,step:10,spacing:1e4},u[l.THIRTY_SECONDLY]={datefield:h.DATEFIELD_SS,step:30,spacing:3e4},u[l.MINUTELY]={datefield:h.DATEFIELD_MM,step:1,spacing:6e4},u[l.TWO_MINUTELY]={datefield:h.DATEFIELD_MM,step:2,spacing:12e4},u[l.FIVE_MINUTELY]={datefield:h.DATEFIELD_MM,step:5,spacing:3e5},u[l.TEN_MINUTELY]={datefield:h.DATEFIELD_MM,step:10,spacing:6e5},u[l.THIRTY_MINUTELY]={datefield:h.DATEFIELD_MM,step:30,spacing:18e5},u[l.HOURLY]={datefield:h.DATEFIELD_HH,step:1,spacing:36e5},u[l.TWO_HOURLY]={datefield:h.DATEFIELD_HH,step:2,spacing:72e5},u[l.SIX_HOURLY]={datefield:h.DATEFIELD_HH,step:6,spacing:216e5},u[l.DAILY]={datefield:h.DATEFIELD_D,step:1,spacing:864e5},u[l.TWO_DAILY]={datefield:h.DATEFIELD_D,step:2,spacing:1728e5},u[l.WEEKLY]={datefield:h.DATEFIELD_D,step:7,spacing:6048e5},u[l.MONTHLY]={datefield:h.DATEFIELD_M,step:1,spacing:2629817280},u[l.QUARTERLY]={datefield:h.DATEFIELD_M,step:3,spacing:216e5*365.2524},u[l.BIANNUAL]={datefield:h.DATEFIELD_M,step:6,spacing:432e5*365.2524},u[l.ANNUAL]={datefield:h.DATEFIELD_Y,step:1,spacing:864e5*365.2524},u[l.DECADAL]={datefield:h.DATEFIELD_Y,step:10,spacing:315578073600},u[l.CENTENNIAL]={datefield:h.DATEFIELD_Y,step:100,spacing:3155780736e3};var d=function(){for(var t=[],e=-39;e<=39;e++)for(var a=Math.pow(10,e),i=1;i<=9;i++){var n=a*i;t.push(n)}return t}(),c=function(t,e,a,i){for(var n=i("pixelsPerLabel"),r=0;r<l.NUM_GRANULARITIES;r++){if(a/p(t,e,r)>=n)return r}return-1},p=function(t,e,a){var i=u[a].spacing;return Math.round(1*(e-t)/i)},g=function(t,e,a,i,r){var o=i("axisLabelFormatter"),s=i("labelsUTC"),d=s?n.DateAccessorsUTC:n.DateAccessorsLocal,c=u[a].datefield,p=u[a].step,g=u[a].spacing,f=new Date(t),_=[];_[h.DATEFIELD_Y]=d.getFullYear(f),_[h.DATEFIELD_M]=d.getMonth(f),_[h.DATEFIELD_D]=d.getDate(f),_[h.DATEFIELD_HH]=d.getHours(f),_[h.DATEFIELD_MM]=d.getMinutes(f),_[h.DATEFIELD_SS]=d.getSeconds(f),_[h.DATEFIELD_MS]=d.getMilliseconds(f);var v=_[c]%p;a==l.WEEKLY&&(v=d.getDay(f)),_[c]-=v;for(var y=c+1;y<h.NUM_DATEFIELDS;y++)_[y]=y===h.DATEFIELD_D?1:0;var x=[],m=d.makeDate.apply(null,_),b=m.getTime();if(a<=l.HOURLY)for(b<t&&(b+=g,m=new Date(b));b<=e;)x.push({v:b,label:o.call(r,m,a,i,r)}),b+=g,m=new Date(b);else for(b<t&&(_[c]+=p,m=d.makeDate.apply(null,_),b=m.getTime());b<=e;)(a>=l.DAILY||d.getHours(m)%p==0)&&x.push({v:b,label:o.call(r,m,a,i,r)}),_[c]+=p,m=d.makeDate.apply(null,_),b=m.getTime();return x};a.getDateAxis=g},{"./dygraph-utils":17}],17:[function(t,e,a){"use strict";function i(t,e,a){t.removeEventListener(e,a,!1)}function n(t){return t=t||window.event,t.stopPropagation&&t.stopPropagation(),t.preventDefault&&t.preventDefault(),t.cancelBubble=!0,t.cancel=!0,t.returnValue=!1,!1}function r(t,e,a){var i,n,r;if(0===e)i=a,n=a,r=a;else{var o=Math.floor(6*t),s=6*t-o,l=a*(1-e),h=a*(1-e*s),u=a*(1-e*(1-s));switch(o){case 1:i=h,n=a,r=l;break;case 2:i=l,n=a,r=u;break;case 3:i=l,n=h,r=a;break;case 4:i=u,n=l,r=a;break;case 5:i=a,n=l,r=h;break;case 6:case 0:i=a,n=u,r=l}}return i=Math.floor(255*i+.5),n=Math.floor(255*n+.5),r=Math.floor(255*r+.5),"rgb("+i+","+n+","+r+")"}function o(t){var e=t.getBoundingClientRect(),a=window,i=document.documentElement;return{x:e.left+(a.pageXOffset||i.scrollLeft),y:e.top+(a.pageYOffset||i.scrollTop)}}function s(t){return!t.pageX||t.pageX<0?0:t.pageX}function l(t){return!t.pageY||t.pageY<0?0:t.pageY}function h(t,e){return s(t)-e.px}function u(t,e){return l(t)-e.py}function d(t){return!!t&&!isNaN(t)}function c(t,e){return!!t&&(null!==t.yval&&(null!==t.x&&void 0!==t.x&&(null!==t.y&&void 0!==t.y&&!(isNaN(t.x)||!e&&isNaN(t.y)))))}function p(t,e){var a=Math.min(Math.max(1,e||2),21);return Math.abs(t)<.001&&0!==t?t.toExponential(a-1):t.toPrecision(a)}function g(t){return t<10?"0"+t:""+t}function f(t,e,a,i){var n=g(t)+":"+g(e);if(a&&(n+=":"+g(a),i)){var r=""+i;n+="."+("000"+r).substring(r.length)}return n}function _(t,e){var a=e?tt:$,i=new Date(t),n=a.getFullYear(i),r=a.getMonth(i),o=a.getDate(i),s=a.getHours(i),l=a.getMinutes(i),h=a.getSeconds(i),u=a.getMilliseconds(i),d=""+n,c=g(r+1),p=g(o),_=3600*s+60*l+h+.001*u,v=d+"/"+c+"/"+p;return _&&(v+=" "+f(s,l,h,u)),v}function v(t,e){var a=Math.pow(10,e);return Math.round(t*a)/a}function y(t,e,a,i,n){for(var r=!0;r;){var o=t,s=e,l=a,h=i,u=n;if(r=!1,null!==h&&void 0!==h&&null!==u&&void 0!==u||(h=0,u=s.length-1),h>u)return-1;null!==l&&void 0!==l||(l=0);var d,c=function(t){return t>=0&&t<s.length},p=parseInt((h+u)/2,10),g=s[p];if(g==o)return p;if(g>o){if(l>0&&(d=p-1,c(d)&&s[d]<o))return p;t=o,e=s,a=l,i=h,n=p-1,r=!0,c=p=g=d=void 0}else{if(!(g<o))return-1;if(l<0&&(d=p+1,c(d)&&s[d]>o))return p;t=o,e=s,a=l,i=p+1,n=u,r=!0,c=p=g=d=void 0}}}function x(t){var e,a;if((-1==t.search("-")||-1!=t.search("T")||-1!=t.search("Z"))&&(a=m(t))&&!isNaN(a))return a;if(-1!=t.search("-")){for(e=t.replace("-","/","g");-1!=e.search("-");)e=e.replace("-","/");a=m(e)}else 8==t.length?(e=t.substr(0,4)+"/"+t.substr(4,2)+"/"+t.substr(6,2),a=m(e)):a=m(t);return a&&!isNaN(a)||console.error("Couldn't parse "+t+" as a date"),a}function m(t){return new Date(t).getTime()}function b(t,e){if(void 0!==e&&null!==e)for(var a in e)e.hasOwnProperty(a)&&(t[a]=e[a]);return t}function w(t,e){if(void 0!==e&&null!==e)for(var a in e)e.hasOwnProperty(a)&&(null===e[a]?t[a]=null:A(e[a])?t[a]=e[a].slice():!function(t){return"object"==typeof Node?t instanceof Node:"object"==typeof t&&"number"==typeof t.nodeType&&"string"==typeof t.nodeName}(e[a])&&"object"==typeof e[a]?("object"==typeof t[a]&&null!==t[a]||(t[a]={}),w(t[a],e[a])):t[a]=e[a]);return t}function A(t){var e=typeof t;return("object"==e||"function"==e&&"function"==typeof t.item)&&null!==t&&"number"==typeof t.length&&3!==t.nodeType}function O(t){return"object"==typeof t&&null!==t&&"function"==typeof t.getTime}function D(t){for(var e=[],a=0;a<t.length;a++)A(t[a])?e.push(D(t[a])):e.push(t[a]);return e}function E(){return document.createElement("canvas")}function L(t){try{var e=window.devicePixelRatio,a=t.webkitBackingStorePixelRatio||t.mozBackingStorePixelRatio||t.msBackingStorePixelRatio||t.oBackingStorePixelRatio||t.backingStorePixelRatio||1;return void 0!==e?e/a:1}catch(t){return 1}}function T(t,e,a,i){e=e||0,a=a||t.length,this.hasNext=!0,this.peek=null,this.start_=e,this.array_=t,this.predicate_=i,this.end_=Math.min(t.length,e+a),this.nextIdx_=e-1,this.next()}function S(t,e,a,i){return new T(t,e,a,i)}function P(t,e,a,i){var n,r=0,o=(new Date).getTime();if(t(r),1==e)return void i();var s=e-1;!function l(){r>=e||et.call(window,function(){var e=(new Date).getTime(),h=e-o;n=r,r=Math.floor(h/a);var u=r-n;r+u>s||r>=s?(t(s),i()):(0!==u&&t(r),l())})}()}function C(t,e){var a={};if(t)for(var i=1;i<t.length;i++)a[t[i]]=!0;var n=function(t){for(var e in t)if(t.hasOwnProperty(e)&&!at[e])return!0;return!1};for(var r in e)if(e.hasOwnProperty(r))if("highlightSeriesOpts"==r||a[r]&&!e.series){if(n(e[r]))return!0}else if("series"==r||"axes"==r){var o=e[r];for(var s in o)if(o.hasOwnProperty(s)&&n(o[s]))return!0}else if(!at[r])return!0;return!1}function M(t){for(var e=0;e<t.length;e++){var a=t.charAt(e);if("\r"===a)return e+1<t.length&&"\n"===t.charAt(e+1)?"\r\n":a;if("\n"===a)return e+1<t.length&&"\r"===t.charAt(e+1)?"\n\r":a}return null}function N(t,e){if(null===e||null===t)return!1;for(var a=t;a&&a!==e;)a=a.parentNode;return a===e}function F(t,e){return e<0?1/Math.pow(t,-e):Math.pow(t,e)}function k(t){var e=nt.exec(t);if(!e)return null;var a=parseInt(e[1],10),i=parseInt(e[2],10),n=parseInt(e[3],10);return e[4]?{r:a,g:i,b:n,a:parseFloat(e[4])}:{r:a,g:i,b:n}}function R(t){var e=k(t);if(e)return e;var a=document.createElement("div");a.style.backgroundColor=t,a.style.visibility="hidden",document.body.appendChild(a);var i=window.getComputedStyle(a,null).backgroundColor;return document.body.removeChild(a),k(i)}function I(t){try{(t||document.createElement("canvas")).getContext("2d")}catch(t){return!1}return!0}function H(t,e,a){var i=parseFloat(t);if(!isNaN(i))return i;if(/^ *$/.test(t))return null;if(/^ *nan *$/i.test(t))return NaN;var n="Unable to parse '"+t+"' as a number";return void 0!==a&&void 0!==e&&(n+=" on line "+(1+(e||0))+" ('"+a+"') of CSV."),console.error(n),null}function Y(t,e){var a=e("sigFigs");if(null!==a)return p(t,a);var i,n=e("digitsAfterDecimal"),r=e("maxNumberWidth"),o=e("labelsKMB"),s=e("labelsKMG2");if(i=0!==t&&(Math.abs(t)>=Math.pow(10,r)||Math.abs(t)<Math.pow(10,-n))?t.toExponential(n):""+v(t,n),o||s){var l,h=[],u=[];o&&(l=1e3,h=rt),s&&(o&&console.warn("Setting both labelsKMB and labelsKMG2. Pick one!"),l=1024,h=ot,u=st);for(var d=Math.abs(t),c=F(l,h.length),g=h.length-1;g>=0;g--,c/=l)if(d>=c){i=v(t/c,n)+h[g];break}if(s){var f=String(t.toExponential()).split("e-");2===f.length&&f[1]>=3&&f[1]<=24&&(i=f[1]%3>0?v(f[0]/F(10,f[1]%3),n):Number(f[0]).toFixed(2),i+=u[Math.floor(f[1]/3)-1])}}return i}function X(t,e,a){return Y.call(this,t,a)}function V(t,e,a){var i=a("labelsUTC"),n=i?tt:$,r=n.getFullYear(t),o=n.getMonth(t),s=n.getDate(t),l=n.getHours(t),h=n.getMinutes(t),u=n.getSeconds(t),d=n.getMilliseconds(t);if(e>=G.Granularity.DECADAL)return""+r;if(e>=G.Granularity.MONTHLY)return lt[o]+"&#160;"+r;if(0===3600*l+60*h+u+.001*d||e>=G.Granularity.DAILY)return g(s)+"&#160;"+lt[o];if(e<G.Granularity.SECONDLY){var c=""+d;return g(u)+"."+("000"+c).substring(c.length)}return e>G.Granularity.MINUTELY?f(l,h,u,0):f(l,h,u,d)}function Z(t,e){return _(t,e("labelsUTC"))}Object.defineProperty(a,"__esModule",{value:!0}),a.removeEvent=i,a.cancelEvent=n,a.hsvToRGB=r,a.findPos=o,a.pageX=s,a.pageY=l,a.dragGetX_=h,a.dragGetY_=u,a.isOK=d,a.isValidPoint=c,a.floatFormat=p,a.zeropad=g,a.hmsString_=f,a.dateString_=_,a.round_=v,a.binarySearch=y,a.dateParser=x,a.dateStrToMillis=m,a.update=b,a.updateDeep=w,a.isArrayLike=A,a.isDateLike=O,a.clone=D,a.createCanvas=E,a.getContextPixelRatio=L,a.Iterator=T,a.createIterator=S,a.repeatAndCleanup=P,a.isPixelChangingOptionList=C,a.detectLineDelimiter=M,a.isNodeContainedBy=N,a.pow=F,a.toRGB_=R,a.isCanvasSupported=I,a.parseFloat_=H,a.numberValueFormatter=Y,a.numberAxisLabelFormatter=X,a.dateAxisLabelFormatter=V,a.dateValueFormatter=Z;var B=t("./dygraph-tickers"),G=function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e.default=t,e}(B);a.LOG_SCALE=10;var W=Math.log(10);a.LN_TEN=W;var U=function(t){return Math.log(t)/W};a.log10=U;var z=function(t,e,a){var i=U(t),n=U(e),r=i+a*(n-i);return Math.pow(10,r)};a.logRangeFraction=z;var j=[2,2];a.DOTTED_LINE=j;var K=[7,3];a.DASHED_LINE=K;var q=[7,2,2,2];a.DOT_DASH_LINE=q;a.HORIZONTAL=1;a.VERTICAL=2;var Q=function(t){return t.getContext("2d")};a.getContext=Q;var J=function(t,e,a){t.addEventListener(e,a,!1)};a.addEvent=J;var $={getFullYear:function(t){return t.getFullYear()},getMonth:function(t){return t.getMonth()},getDate:function(t){return t.getDate()},getHours:function(t){return t.getHours()},getMinutes:function(t){return t.getMinutes()},getSeconds:function(t){return t.getSeconds()},getMilliseconds:function(t){return t.getMilliseconds()},getDay:function(t){return t.getDay()},makeDate:function(t,e,a,i,n,r,o){return new Date(t,e,a,i,n,r,o)}};a.DateAccessorsLocal=$;var tt={getFullYear:function(t){return t.getUTCFullYear()},getMonth:function(t){return t.getUTCMonth()},getDate:function(t){return t.getUTCDate()},getHours:function(t){return t.getUTCHours()},getMinutes:function(t){return t.getUTCMinutes()},getSeconds:function(t){return t.getUTCSeconds()},getMilliseconds:function(t){return t.getUTCMilliseconds()},getDay:function(t){return t.getUTCDay()},makeDate:function(t,e,a,i,n,r,o){return new Date(Date.UTC(t,e,a,i,n,r,o))}};a.DateAccessorsUTC=tt,T.prototype.next=function(){if(!this.hasNext)return null;for(var t=this.peek,e=this.nextIdx_+1,a=!1;e<this.end_;){if(!this.predicate_||this.predicate_(this.array_,e)){this.peek=this.array_[e],a=!0;break}e++}return this.nextIdx_=e,a||(this.hasNext=!1,this.peek=null),t};var et=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){window.setTimeout(t,1e3/60)}}();a.requestAnimFrame=et;var at={annotationClickHandler:!0,annotationDblClickHandler:!0,annotationMouseOutHandler:!0,annotationMouseOverHandler:!0,axisLineColor:!0,axisLineWidth:!0,clickCallback:!0,drawCallback:!0,drawHighlightPointCallback:!0,drawPoints:!0,drawPointCallback:!0,drawGrid:!0,fillAlpha:!0,gridLineColor:!0,gridLineWidth:!0,hideOverlayOnMouseOut:!0,highlightCallback:!0,highlightCircleSize:!0,interactionModel:!0,labelsDiv:!0,labelsKMB:!0,labelsKMG2:!0,labelsSeparateLines:!0,labelsShowZeroValues:!0,legend:!0,panEdgeFraction:!0,pixelsPerYLabel:!0,pointClickCallback:!0,pointSize:!0,rangeSelectorPlotFillColor:!0,rangeSelectorPlotFillGradientColor:!0,rangeSelectorPlotStrokeColor:!0,rangeSelectorBackgroundStrokeColor:!0,rangeSelectorBackgroundLineWidth:!0,rangeSelectorPlotLineWidth:!0,rangeSelectorForegroundStrokeColor:!0,rangeSelectorForegroundLineWidth:!0,rangeSelectorAlpha:!0,showLabelsOnHighlight:!0,showRoller:!0,strokeWidth:!0,underlayCallback:!0,unhighlightCallback:!0,zoomCallback:!0},it={DEFAULT:function(t,e,a,i,n,r,o){a.beginPath(),a.fillStyle=r,a.arc(i,n,o,0,2*Math.PI,!1),a.fill()}};a.Circles=it;var nt=/^rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(?:,\s*([01](?:\.\d+)?))?\)$/,rt=["K","M","B","T","Q"],ot=["k","M","G","T","P","E","Z","Y"],st=["m","u","n","p","f","a","z","y"],lt=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]},{"./dygraph-tickers":16}],18:[function(t,e,a){(function(i){"use strict";function n(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e.default=t,e}function r(t){return t&&t.__esModule?t:{default:t}}function o(t){var e=t[0],a=e[0];if("number"!=typeof a&&!x.isDateLike(a))throw new Error("Expected number or date but got "+typeof a+": "+a+".");for(var i=1;i<e.length;i++){var n=e[i];if(null!==n&&void 0!==n&&("number"!=typeof n&&!x.isArrayLike(n)))throw new Error("Expected number or array but got "+typeof n+": "+n+".")}}Object.defineProperty(a,"__esModule",{value:!0});var s=function(){function t(t,e){var a=[],i=!0,n=!1,r=void 0;try{for(var o,s=t[Symbol.iterator]();!(i=(o=s.next()).done)&&(a.push(o.value),!e||a.length!==e);i=!0);}catch(t){n=!0,r=t}finally{try{!i&&s.return&&s.return()}finally{if(n)throw r}}return a}return function(e,a){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return t(e,a);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),l=t("./dygraph-layout"),h=r(l),u=t("./dygraph-canvas"),d=r(u),c=t("./dygraph-options"),p=r(c),g=t("./dygraph-interaction-model"),f=r(g),_=t("./dygraph-tickers"),v=n(_),y=t("./dygraph-utils"),x=n(y),m=t("./dygraph-default-attrs"),b=r(m),w=t("./dygraph-options-reference"),A=(r(w),t("./iframe-tarp")),O=r(A),D=t("./datahandler/default"),E=r(D),L=t("./datahandler/bars-error"),T=r(L),S=t("./datahandler/bars-custom"),P=r(S),C=t("./datahandler/default-fractions"),M=r(C),N=t("./datahandler/bars-fractions"),F=r(N),k=t("./datahandler/bars"),R=r(k),I=t("./plugins/annotations"),H=r(I),Y=t("./plugins/axes"),X=r(Y),V=t("./plugins/chart-labels"),Z=r(V),B=t("./plugins/grid"),G=r(B),W=t("./plugins/legend"),U=r(W),z=t("./plugins/range-selector"),j=r(z),K=t("./dygraph-gviz"),q=r(K),Q=function(t,e,a){this.__init__(t,e,a)};Q.NAME="Dygraph",Q.VERSION="2.1.0",Q.DEFAULT_ROLL_PERIOD=1,Q.DEFAULT_WIDTH=480,Q.DEFAULT_HEIGHT=320,Q.ANIMATION_STEPS=12,Q.ANIMATION_DURATION=200,Q.Plotters=d.default._Plotters,Q.addedAnnotationCSS=!1,Q.prototype.__init__=function(t,e,a){if(this.is_initial_draw_=!0,this.readyFns_=[],null!==a&&void 0!==a||(a={}),a=Q.copyUserAttrs_(a),"string"==typeof t&&(t=document.getElementById(t)),!t)throw new Error("Constructing dygraph with a non-existent div!");this.maindiv_=t,this.file_=e,this.rollPeriod_=a.rollPeriod||Q.DEFAULT_ROLL_PERIOD,this.previousVerticalX_=-1,this.fractions_=a.fractions||!1,this.dateWindow_=a.dateWindow||null,this.annotations_=[],t.innerHTML="",""===t.style.width&&a.width&&(t.style.width=a.width+"px"),""===t.style.height&&a.height&&(t.style.height=a.height+"px"),""===t.style.height&&0===t.clientHeight&&(t.style.height=Q.DEFAULT_HEIGHT+"px",""===t.style.width&&(t.style.width=Q.DEFAULT_WIDTH+"px")),this.width_=t.clientWidth||a.width||0,this.height_=t.clientHeight||a.height||0,a.stackedGraph&&(a.fillGraph=!0),this.user_attrs_={},x.update(this.user_attrs_,a),this.attrs_={},x.updateDeep(this.attrs_,b.default),this.boundaryIds_=[],this.setIndexByName_={},this.datasetIndex_=[],this.registeredEvents_=[],this.eventListeners_={},this.attributes_=new p.default(this),this.createInterface_(),this.plugins_=[];for(var i=Q.PLUGINS.concat(this.getOption("plugins")),n=0;n<i.length;n++){var r,o=i[n];r=void 0!==o.activate?o:new o;var s={plugin:r,events:{},options:{},pluginOptions:{}},l=r.activate(this);for(var h in l)l.hasOwnProperty(h)&&(s.events[h]=l[h]);this.plugins_.push(s)}for(var n=0;n<this.plugins_.length;n++){var u=this.plugins_[n];for(var h in u.events)if(u.events.hasOwnProperty(h)){var d=u.events[h],c=[u.plugin,d];h in this.eventListeners_?this.eventListeners_[h].push(c):this.eventListeners_[h]=[c]}}this.createDragInterface_(),this.start_()},Q.prototype.cascadeEvents_=function(t,e){if(!(t in this.eventListeners_))return!1;var a={dygraph:this,cancelable:!1,defaultPrevented:!1,preventDefault:function(){if(!a.cancelable)throw"Cannot call preventDefault on non-cancelable event.";a.defaultPrevented=!0},propagationStopped:!1,stopPropagation:function(){a.propagationStopped=!0}};x.update(a,e);var i=this.eventListeners_[t];if(i)for(var n=i.length-1;n>=0;n--){var r=i[n][0],o=i[n][1];if(o.call(r,a),a.propagationStopped)break}return a.defaultPrevented},Q.prototype.getPluginInstance_=function(t){for(var e=0;e<this.plugins_.length;e++){var a=this.plugins_[e];if(a.plugin instanceof t)return a.plugin}return null},Q.prototype.isZoomed=function(t){var e=!!this.dateWindow_;if("x"===t)return e;var a=this.axes_.map(function(t){return!!t.valueRange}).indexOf(!0)>=0;if(null===t||void 0===t)return e||a;if("y"===t)return a;throw new Error("axis parameter is ["+t+"] must be null, 'x' or 'y'.")},Q.prototype.toString=function(){var t=this.maindiv_;return"[Dygraph "+(t&&t.id?t.id:t)+"]"},Q.prototype.attr_=function(t,e){return e?this.attributes_.getForSeries(t,e):this.attributes_.get(t)},Q.prototype.getOption=function(t,e){return this.attr_(t,e)},Q.prototype.getNumericOption=function(t,e){return this.getOption(t,e)},Q.prototype.getStringOption=function(t,e){return this.getOption(t,e)},Q.prototype.getBooleanOption=function(t,e){return this.getOption(t,e)},Q.prototype.getFunctionOption=function(t,e){return this.getOption(t,e)},Q.prototype.getOptionForAxis=function(t,e){return this.attributes_.getForAxis(t,e)},Q.prototype.optionsViewForAxis_=function(t){var e=this;return function(a){var i=e.user_attrs_.axes;return i&&i[t]&&i[t].hasOwnProperty(a)?i[t][a]:("x"!==t||"logscale"!==a)&&(void 0!==e.user_attrs_[a]?e.user_attrs_[a]:(i=e.attrs_.axes,i&&i[t]&&i[t].hasOwnProperty(a)?i[t][a]:"y"==t&&e.axes_[0].hasOwnProperty(a)?e.axes_[0][a]:"y2"==t&&e.axes_[1].hasOwnProperty(a)?e.axes_[1][a]:e.attr_(a)))}},Q.prototype.rollPeriod=function(){return this.rollPeriod_},Q.prototype.xAxisRange=function(){return this.dateWindow_?this.dateWindow_:this.xAxisExtremes()},Q.prototype.xAxisExtremes=function(){var t=this.getNumericOption("xRangePad")/this.plotter_.area.w;if(0===this.numRows())return[0-t,1+t];var e=this.rawData_[0][0],a=this.rawData_[this.rawData_.length-1][0];if(t){var i=a-e;e-=i*t,a+=i*t}return[e,a]},Q.prototype.yAxisExtremes=function(){var t=this.gatherDatasets_(this.rolledSeries_,null),e=t.extremes,a=this.axes_;this.computeYAxisRanges_(e);var i=this.axes_;return this.axes_=a,i.map(function(t){return t.extremeRange})},Q.prototype.yAxisRange=function(t){if(void 0===t&&(t=0),t<0||t>=this.axes_.length)return null;var e=this.axes_[t];return[e.computedValueRange[0],e.computedValueRange[1]]},Q.prototype.yAxisRanges=function(){for(var t=[],e=0;e<this.axes_.length;e++)t.push(this.yAxisRange(e));return t},Q.prototype.toDomCoords=function(t,e,a){return[this.toDomXCoord(t),this.toDomYCoord(e,a)]},Q.prototype.toDomXCoord=function(t){if(null===t)return null;var e=this.plotter_.area,a=this.xAxisRange();return e.x+(t-a[0])/(a[1]-a[0])*e.w},Q.prototype.toDomYCoord=function(t,e){var a=this.toPercentYCoord(t,e);if(null===a)return null;var i=this.plotter_.area;return i.y+a*i.h},Q.prototype.toDataCoords=function(t,e,a){return[this.toDataXCoord(t),this.toDataYCoord(e,a)]},Q.prototype.toDataXCoord=function(t){if(null===t)return null;var e=this.plotter_.area,a=this.xAxisRange();if(this.attributes_.getForAxis("logscale","x")){var i=(t-e.x)/e.w;return x.logRangeFraction(a[0],a[1],i)}return a[0]+(t-e.x)/e.w*(a[1]-a[0])},Q.prototype.toDataYCoord=function(t,e){if(null===t)return null;var a=this.plotter_.area,i=this.yAxisRange(e);if(void 0===e&&(e=0),this.attributes_.getForAxis("logscale",e)){var n=(t-a.y)/a.h;return x.logRangeFraction(i[1],i[0],n)}return i[0]+(a.y+a.h-t)/a.h*(i[1]-i[0])},Q.prototype.toPercentYCoord=function(t,e){if(null===t)return null;void 0===e&&(e=0);var a,i=this.yAxisRange(e);if(this.attributes_.getForAxis("logscale",e)){var n=x.log10(i[0]),r=x.log10(i[1]);a=(r-x.log10(t))/(r-n)}else a=(i[1]-t)/(i[1]-i[0]);return a},Q.prototype.toPercentXCoord=function(t){if(null===t)return null;var e,a=this.xAxisRange();if(!0===this.attributes_.getForAxis("logscale","x")){var i=x.log10(a[0]),n=x.log10(a[1]);e=(x.log10(t)-i)/(n-i)}else e=(t-a[0])/(a[1]-a[0]);return e},Q.prototype.numColumns=function(){return this.rawData_?this.rawData_[0]?this.rawData_[0].length:this.attr_("labels").length:0},Q.prototype.numRows=function(){return this.rawData_?this.rawData_.length:0},Q.prototype.getValue=function(t,e){return t<0||t>this.rawData_.length?null:e<0||e>this.rawData_[t].length?null:this.rawData_[t][e]},Q.prototype.createInterface_=function(){var t=this.maindiv_;this.graphDiv=document.createElement("div"),this.graphDiv.style.textAlign="left",this.graphDiv.style.position="relative",t.appendChild(this.graphDiv),this.canvas_=x.createCanvas(),this.canvas_.style.position="absolute",this.hidden_=this.createPlotKitCanvas_(this.canvas_),this.canvas_ctx_=x.getContext(this.canvas_),this.hidden_ctx_=x.getContext(this.hidden_),this.resizeElements_(),this.graphDiv.appendChild(this.hidden_),this.graphDiv.appendChild(this.canvas_),this.mouseEventElement_=this.createMouseEventElement_(),this.layout_=new h.default(this);var e=this;this.mouseMoveHandler_=function(t){e.mouseMove_(t)},this.mouseOutHandler_=function(t){var a=t.target||t.fromElement,i=t.relatedTarget||t.toElement;x.isNodeContainedBy(a,e.graphDiv)&&!x.isNodeContainedBy(i,e.graphDiv)&&e.mouseOut_(t)},this.addAndTrackEvent(window,"mouseout",this.mouseOutHandler_),this.addAndTrackEvent(this.mouseEventElement_,"mousemove",this.mouseMoveHandler_),this.resizeHandler_||(this.resizeHandler_=function(t){e.resize()},this.addAndTrackEvent(window,"resize",this.resizeHandler_))},Q.prototype.resizeElements_=function(){this.graphDiv.style.width=this.width_+"px",this.graphDiv.style.height=this.height_+"px";var t=this.getNumericOption("pixelRatio"),e=t||x.getContextPixelRatio(this.canvas_ctx_);this.canvas_.width=this.width_*e,this.canvas_.height=this.height_*e,this.canvas_.style.width=this.width_+"px",this.canvas_.style.height=this.height_+"px",1!==e&&this.canvas_ctx_.scale(e,e);var a=t||x.getContextPixelRatio(this.hidden_ctx_);this.hidden_.width=this.width_*a,this.hidden_.height=this.height_*a,this.hidden_.style.width=this.width_+"px",this.hidden_.style.height=this.height_+"px",1!==a&&this.hidden_ctx_.scale(a,a)},Q.prototype.destroy=function(){this.canvas_ctx_.restore(),this.hidden_ctx_.restore();for(var t=this.plugins_.length-1;t>=0;t--){var e=this.plugins_.pop();e.plugin.destroy&&e.plugin.destroy()}this.removeTrackedEvents_(),x.removeEvent(window,"mouseout",this.mouseOutHandler_),x.removeEvent(this.mouseEventElement_,"mousemove",this.mouseMoveHandler_),x.removeEvent(window,"resize",this.resizeHandler_),this.resizeHandler_=null,function t(e){for(;e.hasChildNodes();)t(e.firstChild),e.removeChild(e.firstChild)}(this.maindiv_);var a=function(t){for(var e in t)"object"==typeof t[e]&&(t[e]=null)};a(this.layout_),a(this.plotter_),a(this)},Q.prototype.createPlotKitCanvas_=function(t){var e=x.createCanvas();return e.style.position="absolute",e.style.top=t.style.top,e.style.left=t.style.left,
+e.width=this.width_,e.height=this.height_,e.style.width=this.width_+"px",e.style.height=this.height_+"px",e},Q.prototype.createMouseEventElement_=function(){return this.canvas_},Q.prototype.setColors_=function(){var t=this.getLabels(),e=t.length-1;this.colors_=[],this.colorsMap_={};for(var a=this.getNumericOption("colorSaturation")||1,i=this.getNumericOption("colorValue")||.5,n=Math.ceil(e/2),r=this.getOption("colors"),o=this.visibility(),s=0;s<e;s++)if(o[s]){var l=t[s+1],h=this.attributes_.getForSeries("color",l);if(!h)if(r)h=r[s%r.length];else{var u=s%2?n+(s+1)/2:Math.ceil((s+1)/2),d=1*u/(1+e);h=x.hsvToRGB(d,a,i)}this.colors_.push(h),this.colorsMap_[l]=h}},Q.prototype.getColors=function(){return this.colors_},Q.prototype.getPropertiesForSeries=function(t){for(var e=-1,a=this.getLabels(),i=1;i<a.length;i++)if(a[i]==t){e=i;break}return-1==e?null:{name:t,column:e,visible:this.visibility()[e-1],color:this.colorsMap_[t],axis:1+this.attributes_.axisForSeries(t)}},Q.prototype.createRollInterface_=function(){var t=this,e=this.roller_;e||(this.roller_=e=document.createElement("input"),e.type="text",e.style.display="none",e.className="dygraph-roller",this.graphDiv.appendChild(e));var a=this.getBooleanOption("showRoller")?"block":"none",i=this.getArea(),n={top:i.y+i.h-25+"px",left:i.x+1+"px",display:a};e.size="2",e.value=this.rollPeriod_,x.update(e.style,n),e.onchange=function(){return t.adjustRoll(e.value)}},Q.prototype.createDragInterface_=function(){var t={isZooming:!1,isPanning:!1,is2DPan:!1,dragStartX:null,dragStartY:null,dragEndX:null,dragEndY:null,dragDirection:null,prevEndX:null,prevEndY:null,prevDragDirection:null,cancelNextDblclick:!1,initialLeftmostDate:null,xUnitsPerPixel:null,dateRange:null,px:0,py:0,boundedDates:null,boundedValues:null,tarp:new O.default,initializeMouseDown:function(t,e,a){t.preventDefault?t.preventDefault():(t.returnValue=!1,t.cancelBubble=!0);var i=x.findPos(e.canvas_);a.px=i.x,a.py=i.y,a.dragStartX=x.dragGetX_(t,a),a.dragStartY=x.dragGetY_(t,a),a.cancelNextDblclick=!1,a.tarp.cover()},destroy:function(){var t=this;if((t.isZooming||t.isPanning)&&(t.isZooming=!1,t.dragStartX=null,t.dragStartY=null),t.isPanning){t.isPanning=!1,t.draggingDate=null,t.dateRange=null;for(var e=0;e<a.axes_.length;e++)delete a.axes_[e].draggingValue,delete a.axes_[e].dragValueRange}t.tarp.uncover()}},e=this.getOption("interactionModel"),a=this;for(var i in e)e.hasOwnProperty(i)&&this.addAndTrackEvent(this.mouseEventElement_,i,function(e){return function(i){e(i,a,t)}}(e[i]));if(!e.willDestroyContextMyself){var n=function(e){t.destroy()};this.addAndTrackEvent(document,"mouseup",n)}},Q.prototype.drawZoomRect_=function(t,e,a,i,n,r,o,s){var l=this.canvas_ctx_;r==x.HORIZONTAL?l.clearRect(Math.min(e,o),this.layout_.getPlotArea().y,Math.abs(e-o),this.layout_.getPlotArea().h):r==x.VERTICAL&&l.clearRect(this.layout_.getPlotArea().x,Math.min(i,s),this.layout_.getPlotArea().w,Math.abs(i-s)),t==x.HORIZONTAL?a&&e&&(l.fillStyle="rgba(128,128,128,0.33)",l.fillRect(Math.min(e,a),this.layout_.getPlotArea().y,Math.abs(a-e),this.layout_.getPlotArea().h)):t==x.VERTICAL&&n&&i&&(l.fillStyle="rgba(128,128,128,0.33)",l.fillRect(this.layout_.getPlotArea().x,Math.min(i,n),this.layout_.getPlotArea().w,Math.abs(n-i)))},Q.prototype.clearZoomRect_=function(){this.currentZoomRectArgs_=null,this.canvas_ctx_.clearRect(0,0,this.width_,this.height_)},Q.prototype.doZoomX_=function(t,e){this.currentZoomRectArgs_=null;var a=this.toDataXCoord(t),i=this.toDataXCoord(e);this.doZoomXDates_(a,i)},Q.prototype.doZoomXDates_=function(t,e){var a=this,i=this.xAxisRange(),n=[t,e],r=this.getFunctionOption("zoomCallback");this.doAnimatedZoom(i,n,null,null,function(){r&&r.call(a,t,e,a.yAxisRanges())})},Q.prototype.doZoomY_=function(t,e){var a=this;this.currentZoomRectArgs_=null;for(var i=this.yAxisRanges(),n=[],r=0;r<this.axes_.length;r++){var o=this.toDataYCoord(t,r),l=this.toDataYCoord(e,r);n.push([l,o])}var h=this.getFunctionOption("zoomCallback");this.doAnimatedZoom(null,null,i,n,function(){if(h){var t=a.xAxisRange(),e=s(t,2),i=e[0],n=e[1];h.call(a,i,n,a.yAxisRanges())}})},Q.zoomAnimationFunction=function(t,e){return(1-Math.pow(1.5,-t))/(1-Math.pow(1.5,-e))},Q.prototype.resetZoom=function(){var t=this,e=this.isZoomed("x"),a=this.isZoomed("y"),i=e||a;if(this.clearSelection(),i){var n=this.xAxisExtremes(),r=s(n,2),o=r[0],l=r[1],h=this.getBooleanOption("animatedZooms"),u=this.getFunctionOption("zoomCallback");if(!h)return this.dateWindow_=null,this.axes_.forEach(function(t){t.valueRange&&delete t.valueRange}),this.drawGraph_(),void(u&&u.call(this,o,l,this.yAxisRanges()));var d=null,c=null,p=null,g=null;e&&(d=this.xAxisRange(),c=[o,l]),a&&(p=this.yAxisRanges(),g=this.yAxisExtremes()),this.doAnimatedZoom(d,c,p,g,function(){t.dateWindow_=null,t.axes_.forEach(function(t){t.valueRange&&delete t.valueRange}),u&&u.call(t,o,l,t.yAxisRanges())})}},Q.prototype.doAnimatedZoom=function(t,e,a,i,n){var r,o,s=this,l=this.getBooleanOption("animatedZooms")?Q.ANIMATION_STEPS:1,h=[],u=[];if(null!==t&&null!==e)for(r=1;r<=l;r++)o=Q.zoomAnimationFunction(r,l),h[r-1]=[t[0]*(1-o)+o*e[0],t[1]*(1-o)+o*e[1]];if(null!==a&&null!==i)for(r=1;r<=l;r++){o=Q.zoomAnimationFunction(r,l);for(var d=[],c=0;c<this.axes_.length;c++)d.push([a[c][0]*(1-o)+o*i[c][0],a[c][1]*(1-o)+o*i[c][1]]);u[r-1]=d}x.repeatAndCleanup(function(t){if(u.length)for(var e=0;e<s.axes_.length;e++){var a=u[t][e];s.axes_[e].valueRange=[a[0],a[1]]}h.length&&(s.dateWindow_=h[t]),s.drawGraph_()},l,Q.ANIMATION_DURATION/l,n)},Q.prototype.getArea=function(){return this.plotter_.area},Q.prototype.eventToDomCoords=function(t){if(t.offsetX&&t.offsetY)return[t.offsetX,t.offsetY];var e=x.findPos(this.mouseEventElement_);return[x.pageX(t)-e.x,x.pageY(t)-e.y]},Q.prototype.findClosestRow=function(t){for(var e=1/0,a=-1,i=this.layout_.points,n=0;n<i.length;n++)for(var r=i[n],o=r.length,s=0;s<o;s++){var l=r[s];if(x.isValidPoint(l,!0)){var h=Math.abs(l.canvasx-t);h<e&&(e=h,a=l.idx)}}return a},Q.prototype.findClosestPoint=function(t,e){for(var a,i,n,r,o,s,l,h=1/0,u=this.layout_.points.length-1;u>=0;--u)for(var d=this.layout_.points[u],c=0;c<d.length;++c)r=d[c],x.isValidPoint(r)&&(i=r.canvasx-t,n=r.canvasy-e,(a=i*i+n*n)<h&&(h=a,o=r,s=u,l=r.idx));return{row:l,seriesName:this.layout_.setNames[s],point:o}},Q.prototype.findStackedPoint=function(t,e){for(var a,i,n=this.findClosestRow(t),r=0;r<this.layout_.points.length;++r){var o=this.getLeftBoundary_(r),s=n-o,l=this.layout_.points[r];if(!(s>=l.length)){var h=l[s];if(x.isValidPoint(h)){var u=h.canvasy;if(t>h.canvasx&&s+1<l.length){var d=l[s+1];if(x.isValidPoint(d)){var c=d.canvasx-h.canvasx;if(c>0){var p=(t-h.canvasx)/c;u+=p*(d.canvasy-h.canvasy)}}}else if(t<h.canvasx&&s>0){var g=l[s-1];if(x.isValidPoint(g)){var c=h.canvasx-g.canvasx;if(c>0){var p=(h.canvasx-t)/c;u+=p*(g.canvasy-h.canvasy)}}}(0===r||u<e)&&(a=h,i=r)}}}return{row:n,seriesName:this.layout_.setNames[i],point:a}},Q.prototype.mouseMove_=function(t){var e=this.layout_.points;if(void 0!==e&&null!==e){var a=this.eventToDomCoords(t),i=a[0],n=a[1],r=this.getOption("highlightSeriesOpts"),o=!1;if(r&&!this.isSeriesLocked()){var s;s=this.getBooleanOption("stackedGraph")?this.findStackedPoint(i,n):this.findClosestPoint(i,n),o=this.setSelection(s.row,s.seriesName)}else{var l=this.findClosestRow(i);o=this.setSelection(l)}var h=this.getFunctionOption("highlightCallback");h&&o&&h.call(this,t,this.lastx_,this.selPoints_,this.lastRow_,this.highlightSet_)}},Q.prototype.getLeftBoundary_=function(t){if(this.boundaryIds_[t])return this.boundaryIds_[t][0];for(var e=0;e<this.boundaryIds_.length;e++)if(void 0!==this.boundaryIds_[e])return this.boundaryIds_[e][0];return 0},Q.prototype.animateSelection_=function(t){void 0===this.fadeLevel&&(this.fadeLevel=0),void 0===this.animateId&&(this.animateId=0);var e=this.fadeLevel,a=t<0?e:10-e;if(a<=0)return void(this.fadeLevel&&this.updateSelection_(1));var i=++this.animateId,n=this,r=function(){0!==n.fadeLevel&&t<0&&(n.fadeLevel=0,n.clearSelection())};x.repeatAndCleanup(function(e){n.animateId==i&&(n.fadeLevel+=t,0===n.fadeLevel?n.clearSelection():n.updateSelection_(n.fadeLevel/10))},a,30,r)},Q.prototype.updateSelection_=function(t){this.cascadeEvents_("select",{selectedRow:-1===this.lastRow_?void 0:this.lastRow_,selectedX:-1===this.lastx_?void 0:this.lastx_,selectedPoints:this.selPoints_});var e,a=this.canvas_ctx_;if(this.getOption("highlightSeriesOpts")){a.clearRect(0,0,this.width_,this.height_);var i=1-this.getNumericOption("highlightSeriesBackgroundAlpha"),n=x.toRGB_(this.getOption("highlightSeriesBackgroundColor"));if(i){if(void 0===t)return void this.animateSelection_(1);i*=t,a.fillStyle="rgba("+n.r+","+n.g+","+n.b+","+i+")",a.fillRect(0,0,this.width_,this.height_)}this.plotter_._renderLineChart(this.highlightSet_,a)}else if(this.previousVerticalX_>=0){var r=0,o=this.attr_("labels");for(e=1;e<o.length;e++){var s=this.getNumericOption("highlightCircleSize",o[e]);s>r&&(r=s)}var l=this.previousVerticalX_;a.clearRect(l-r-1,0,2*r+2,this.height_)}if(this.selPoints_.length>0){var h=this.selPoints_[0].canvasx;for(a.save(),e=0;e<this.selPoints_.length;e++){var u=this.selPoints_[e];if(!isNaN(u.canvasy)){var d=this.getNumericOption("highlightCircleSize",u.name),c=this.getFunctionOption("drawHighlightPointCallback",u.name),p=this.plotter_.colors[u.name];c||(c=x.Circles.DEFAULT),a.lineWidth=this.getNumericOption("strokeWidth",u.name),a.strokeStyle=p,a.fillStyle=p,c.call(this,this,u.name,a,h,u.canvasy,p,d,u.idx)}}a.restore(),this.previousVerticalX_=h}},Q.prototype.setSelection=function(t,e,a){this.selPoints_=[];var i=!1;if(!1!==t&&t>=0){t!=this.lastRow_&&(i=!0),this.lastRow_=t;for(var n=0;n<this.layout_.points.length;++n){var r=this.layout_.points[n],o=t-this.getLeftBoundary_(n);if(o>=0&&o<r.length&&r[o].idx==t){var s=r[o];null!==s.yval&&this.selPoints_.push(s)}else for(var l=0;l<r.length;++l){var s=r[l];if(s.idx==t){null!==s.yval&&this.selPoints_.push(s);break}}}}else this.lastRow_>=0&&(i=!0),this.lastRow_=-1;return this.selPoints_.length?this.lastx_=this.selPoints_[0].xval:this.lastx_=-1,void 0!==e&&(this.highlightSet_!==e&&(i=!0),this.highlightSet_=e),void 0!==a&&(this.lockedSet_=a),i&&this.updateSelection_(void 0),i},Q.prototype.mouseOut_=function(t){this.getFunctionOption("unhighlightCallback")&&this.getFunctionOption("unhighlightCallback").call(this,t),this.getBooleanOption("hideOverlayOnMouseOut")&&!this.lockedSet_&&this.clearSelection()},Q.prototype.clearSelection=function(){if(this.cascadeEvents_("deselect",{}),this.lockedSet_=!1,this.fadeLevel)return void this.animateSelection_(-1);this.canvas_ctx_.clearRect(0,0,this.width_,this.height_),this.fadeLevel=0,this.selPoints_=[],this.lastx_=-1,this.lastRow_=-1,this.highlightSet_=null},Q.prototype.getSelection=function(){if(!this.selPoints_||this.selPoints_.length<1)return-1;for(var t=0;t<this.layout_.points.length;t++)for(var e=this.layout_.points[t],a=0;a<e.length;a++)if(e[a].x==this.selPoints_[0].x)return e[a].idx;return-1},Q.prototype.getHighlightSeries=function(){return this.highlightSet_},Q.prototype.isSeriesLocked=function(){return this.lockedSet_},Q.prototype.loadedEvent_=function(t){this.rawData_=this.parseCSV_(t),this.cascadeDataDidUpdateEvent_(),this.predraw_()},Q.prototype.addXTicks_=function(){var t;t=this.dateWindow_?[this.dateWindow_[0],this.dateWindow_[1]]:this.xAxisExtremes();var e=this.optionsViewForAxis_("x"),a=e("ticker")(t[0],t[1],this.plotter_.area.w,e,this);this.layout_.setXTicks(a)},Q.prototype.getHandlerClass_=function(){return this.attr_("dataHandler")?this.attr_("dataHandler"):this.fractions_?this.getBooleanOption("errorBars")?F.default:M.default:this.getBooleanOption("customBars")?P.default:this.getBooleanOption("errorBars")?T.default:E.default},Q.prototype.predraw_=function(){var t=new Date;this.dataHandler_=new(this.getHandlerClass_()),this.layout_.computePlotArea(),this.computeYAxes_(),this.is_initial_draw_||(this.canvas_ctx_.restore(),this.hidden_ctx_.restore()),this.canvas_ctx_.save(),this.hidden_ctx_.save(),this.plotter_=new d.default(this,this.hidden_,this.hidden_ctx_,this.layout_),this.createRollInterface_(),this.cascadeEvents_("predraw"),this.rolledSeries_=[null];for(var e=1;e<this.numColumns();e++){var a=this.dataHandler_.extractSeries(this.rawData_,e,this.attributes_);this.rollPeriod_>1&&(a=this.dataHandler_.rollingAverage(a,this.rollPeriod_,this.attributes_)),this.rolledSeries_.push(a)}this.drawGraph_();var i=new Date;this.drawingTimeMs_=i-t},Q.PointType=void 0,Q.stackPoints_=function(t,e,a,i){for(var n=null,r=null,o=null,s=-1,l=0;l<t.length;++l){var h=t[l],u=h.xval;void 0===e[u]&&(e[u]=0);var d=h.yval;isNaN(d)||null===d?"none"==i?d=0:(!function(e){if(!(s>=e))for(var a=e;a<t.length;++a)if(o=null,!isNaN(t[a].yval)&&null!==t[a].yval){s=a,o=t[a];break}}(l),d=r&&o&&"none"!=i?r.yval+(o.yval-r.yval)*((u-r.xval)/(o.xval-r.xval)):r&&"all"==i?r.yval:o&&"all"==i?o.yval:0):r=h;var c=e[u];n!=u&&(c+=d,e[u]=c),n=u,h.yval_stacked=c,c>a[1]&&(a[1]=c),c<a[0]&&(a[0]=c)}},Q.prototype.gatherDatasets_=function(t,e){var a,i,n,r,o,s,l=[],h=[],u=[],d={},c=t.length-1;for(a=c;a>=1;a--)if(this.visibility()[a-1]){if(e){s=t[a];var p=e[0],g=e[1];for(n=null,r=null,i=0;i<s.length;i++)s[i][0]>=p&&null===n&&(n=i),s[i][0]<=g&&(r=i);null===n&&(n=0);for(var f=n,_=!0;_&&f>0;)f--,_=null===s[f][1];null===r&&(r=s.length-1);var v=r;for(_=!0;_&&v<s.length-1;)v++,_=null===s[v][1];f!==n&&(n=f),v!==r&&(r=v),l[a-1]=[n,r],s=s.slice(n,r+1)}else s=t[a],l[a-1]=[0,s.length-1];var y=this.attr_("labels")[a],x=this.dataHandler_.getExtremeYValues(s,e,this.getBooleanOption("stepPlot",y)),m=this.dataHandler_.seriesToPoints(s,y,l[a-1][0]);this.getBooleanOption("stackedGraph")&&(o=this.attributes_.axisForSeries(y),void 0===u[o]&&(u[o]=[]),Q.stackPoints_(m,u[o],x,this.getBooleanOption("stackedGraphNaNFill"))),d[y]=x,h[a]=m}return{points:h,extremes:d,boundaryIds:l}},Q.prototype.drawGraph_=function(){var t=new Date,e=this.is_initial_draw_;this.is_initial_draw_=!1,this.layout_.removeAllDatasets(),this.setColors_(),this.attrs_.pointSize=.5*this.getNumericOption("highlightCircleSize");var a=this.gatherDatasets_(this.rolledSeries_,this.dateWindow_),i=a.points,n=a.extremes;this.boundaryIds_=a.boundaryIds,this.setIndexByName_={};for(var r=this.attr_("labels"),o=0,s=1;s<i.length;s++)this.visibility()[s-1]&&(this.layout_.addDataset(r[s],i[s]),this.datasetIndex_[s]=o++);for(var s=0;s<r.length;s++)this.setIndexByName_[r[s]]=s;if(this.computeYAxisRanges_(n),this.layout_.setYAxes(this.axes_),this.addXTicks_(),this.layout_.evaluate(),this.renderGraph_(e),this.getStringOption("timingName")){var l=new Date;console.log(this.getStringOption("timingName")+" - drawGraph: "+(l-t)+"ms")}},Q.prototype.renderGraph_=function(t){this.cascadeEvents_("clearChart"),this.plotter_.clear();var e=this.getFunctionOption("underlayCallback");e&&e.call(this,this.hidden_ctx_,this.layout_.getPlotArea(),this,this);var a={canvas:this.hidden_,drawingContext:this.hidden_ctx_};this.cascadeEvents_("willDrawChart",a),this.plotter_.render(),this.cascadeEvents_("didDrawChart",a),this.lastRow_=-1,this.canvas_.getContext("2d").clearRect(0,0,this.width_,this.height_);var i=this.getFunctionOption("drawCallback");if(null!==i&&i.call(this,this,t),t)for(this.readyFired_=!0;this.readyFns_.length>0;){var n=this.readyFns_.pop();n(this)}},Q.prototype.computeYAxes_=function(){var t,e,a;for(this.axes_=[],t=0;t<this.attributes_.numAxes();t++)e={g:this},x.update(e,this.attributes_.axisOptions(t)),this.axes_[t]=e;for(t=0;t<this.axes_.length;t++)if(0===t)e=this.optionsViewForAxis_("y"+(t?"2":"")),(a=e("valueRange"))&&(this.axes_[t].valueRange=a);else{var i=this.user_attrs_.axes;i&&i.y2&&(a=i.y2.valueRange)&&(this.axes_[t].valueRange=a)}},Q.prototype.numAxes=function(){return this.attributes_.numAxes()},Q.prototype.axisPropertiesForSeries=function(t){return this.axes_[this.attributes_.axisForSeries(t)]},Q.prototype.computeYAxisRanges_=function(t){for(var e,a,i,n,r,o=function(t){return isNaN(parseFloat(t))},s=this.attributes_.numAxes(),l=0;l<s;l++){var h=this.axes_[l],u=this.attributes_.getForAxis("logscale",l),d=this.attributes_.getForAxis("includeZero",l),c=this.attributes_.getForAxis("independentTicks",l);i=this.attributes_.seriesForAxis(l),e=!0,n=.1;var p=this.getNumericOption("yRangePad");if(null!==p&&(e=!1,n=p/this.plotter_.area.h),0===i.length)h.extremeRange=[0,1];else{for(var g,f,_=1/0,v=-1/0,y=0;y<i.length;y++)t.hasOwnProperty(i[y])&&(g=t[i[y]][0],null!==g&&(_=Math.min(g,_)),null!==(f=t[i[y]][1])&&(v=Math.max(f,v)));d&&!u&&(_>0&&(_=0),v<0&&(v=0)),_==1/0&&(_=0),v==-1/0&&(v=1),a=v-_,0===a&&(0!==v?a=Math.abs(v):(v=1,a=1));var m=v,b=_;e&&(u?(m=v+n*a,b=_):(m=v+n*a,b=_-n*a,b<0&&_>=0&&(b=0),m>0&&v<=0&&(m=0))),h.extremeRange=[b,m]}if(h.valueRange){var w=o(h.valueRange[0])?h.extremeRange[0]:h.valueRange[0],A=o(h.valueRange[1])?h.extremeRange[1]:h.valueRange[1];h.computedValueRange=[w,A]}else h.computedValueRange=h.extremeRange;if(!e)if(u){w=h.computedValueRange[0],A=h.computedValueRange[1];var O=n/(2*n-1),D=(n-1)/(2*n-1);h.computedValueRange[0]=x.logRangeFraction(w,A,O),h.computedValueRange[1]=x.logRangeFraction(w,A,D)}else w=h.computedValueRange[0],A=h.computedValueRange[1],a=A-w,h.computedValueRange[0]=w-a*n,h.computedValueRange[1]=A+a*n;if(c){h.independentTicks=c;var E=this.optionsViewForAxis_("y"+(l?"2":"")),L=E("ticker");h.ticks=L(h.computedValueRange[0],h.computedValueRange[1],this.plotter_.area.h,E,this),r||(r=h)}}if(void 0===r)throw'Configuration Error: At least one axis has to have the "independentTicks" option activated.';for(var l=0;l<s;l++){var h=this.axes_[l];if(!h.independentTicks){for(var E=this.optionsViewForAxis_("y"+(l?"2":"")),L=E("ticker"),T=r.ticks,S=r.computedValueRange[1]-r.computedValueRange[0],P=h.computedValueRange[1]-h.computedValueRange[0],C=[],M=0;M<T.length;M++){var N=(T[M].v-r.computedValueRange[0])/S,F=h.computedValueRange[0]+N*P;C.push(F)}h.ticks=L(h.computedValueRange[0],h.computedValueRange[1],this.plotter_.area.h,E,this,C)}}},Q.prototype.detectTypeFromString_=function(t){var e=!1,a=t.indexOf("-");a>0&&"e"!=t[a-1]&&"E"!=t[a-1]||t.indexOf("/")>=0||isNaN(parseFloat(t))?e=!0:8==t.length&&t>"19700101"&&t<"20371231"&&(e=!0),this.setXAxisOptions_(e)},Q.prototype.setXAxisOptions_=function(t){t?(this.attrs_.xValueParser=x.dateParser,this.attrs_.axes.x.valueFormatter=x.dateValueFormatter,this.attrs_.axes.x.ticker=v.dateTicker,this.attrs_.axes.x.axisLabelFormatter=x.dateAxisLabelFormatter):(this.attrs_.xValueParser=function(t){return parseFloat(t)},this.attrs_.axes.x.valueFormatter=function(t){return t},this.attrs_.axes.x.ticker=v.numericTicks,this.attrs_.axes.x.axisLabelFormatter=this.attrs_.axes.x.valueFormatter)},Q.prototype.parseCSV_=function(t){var e,a,i=[],n=x.detectLineDelimiter(t),r=t.split(n||"\n"),o=this.getStringOption("delimiter");-1==r[0].indexOf(o)&&r[0].indexOf("\t")>=0&&(o="\t");var s=0;"labels"in this.user_attrs_||(s=1,this.attrs_.labels=r[0].split(o),this.attributes_.reparseSeries());for(var l,h=!1,u=this.attr_("labels").length,d=!1,c=s;c<r.length;c++){var p=r[c];if(c,0!==p.length&&"#"!=p[0]){var g=p.split(o);if(!(g.length<2)){var f=[];if(h||(this.detectTypeFromString_(g[0]),l=this.getFunctionOption("xValueParser"),h=!0),f[0]=l(g[0],this),this.fractions_)for(a=1;a<g.length;a++)e=g[a].split("/"),2!=e.length?(console.error('Expected fractional "num/den" values in CSV data but found a value \''+g[a]+"' on line "+(1+c)+" ('"+p+"') which is not of this form."),f[a]=[0,0]):f[a]=[x.parseFloat_(e[0],c,p),x.parseFloat_(e[1],c,p)];else if(this.getBooleanOption("errorBars"))for(g.length%2!=1&&console.error("Expected alternating (value, stdev.) pairs in CSV data but line "+(1+c)+" has an odd number of values ("+(g.length-1)+"): '"+p+"'"),a=1;a<g.length;a+=2)f[(a+1)/2]=[x.parseFloat_(g[a],c,p),x.parseFloat_(g[a+1],c,p)];else if(this.getBooleanOption("customBars"))for(a=1;a<g.length;a++){var _=g[a];/^ *$/.test(_)?f[a]=[null,null,null]:(e=_.split(";"),3==e.length?f[a]=[x.parseFloat_(e[0],c,p),x.parseFloat_(e[1],c,p),x.parseFloat_(e[2],c,p)]:console.warn('When using customBars, values must be either blank or "low;center;high" tuples (got "'+_+'" on line '+(1+c)))}else for(a=1;a<g.length;a++)f[a]=x.parseFloat_(g[a],c,p);if(i.length>0&&f[0]<i[i.length-1][0]&&(d=!0),f.length!=u&&console.error("Number of columns in line "+c+" ("+f.length+") does not agree with number of labels ("+u+") "+p),0===c&&this.attr_("labels")){var v=!0;for(a=0;v&&a<f.length;a++)f[a]&&(v=!1);if(v){console.warn("The dygraphs 'labels' option is set, but the first row of CSV data ('"+p+"') appears to also contain labels. Will drop the CSV labels and use the option labels.");continue}}i.push(f)}}}return d&&(console.warn("CSV is out of order; order it correctly to speed loading."),i.sort(function(t,e){return t[0]-e[0]})),i},Q.prototype.parseArray_=function(t){if(0===t.length)return console.error("Can't plot empty data set"),null;if(0===t[0].length)return console.error("Data set cannot contain an empty row"),null;o(t);var e;if(null===this.attr_("labels")){for(console.warn("Using default labels. Set labels explicitly via 'labels' in the options parameter"),this.attrs_.labels=["X"],e=1;e<t[0].length;e++)this.attrs_.labels.push("Y"+e);this.attributes_.reparseSeries()}else{var a=this.attr_("labels");if(a.length!=t[0].length)return console.error("Mismatch between number of labels ("+a+") and number of columns in array ("+t[0].length+")"),null}if(x.isDateLike(t[0][0])){this.attrs_.axes.x.valueFormatter=x.dateValueFormatter,this.attrs_.axes.x.ticker=v.dateTicker,this.attrs_.axes.x.axisLabelFormatter=x.dateAxisLabelFormatter;var i=x.clone(t);for(e=0;e<t.length;e++){if(0===i[e].length)return console.error("Row "+(1+e)+" of data is empty"),null;if(null===i[e][0]||"function"!=typeof i[e][0].getTime||isNaN(i[e][0].getTime()))return console.error("x value in row "+(1+e)+" is not a Date"),null;i[e][0]=i[e][0].getTime()}return i}return this.attrs_.axes.x.valueFormatter=function(t){return t},this.attrs_.axes.x.ticker=v.numericTicks,this.attrs_.axes.x.axisLabelFormatter=x.numberAxisLabelFormatter,t},Q.prototype.parseDataTable_=function(t){var e=t.getNumberOfColumns(),a=t.getNumberOfRows(),i=t.getColumnType(0);if("date"==i||"datetime"==i)this.attrs_.xValueParser=x.dateParser,this.attrs_.axes.x.valueFormatter=x.dateValueFormatter,this.attrs_.axes.x.ticker=v.dateTicker,this.attrs_.axes.x.axisLabelFormatter=x.dateAxisLabelFormatter;else{if("number"!=i)throw new Error("only 'date', 'datetime' and 'number' types are supported for column 1 of DataTable input (Got '"+i+"')");this.attrs_.xValueParser=function(t){return parseFloat(t)},this.attrs_.axes.x.valueFormatter=function(t){return t},this.attrs_.axes.x.ticker=v.numericTicks,this.attrs_.axes.x.axisLabelFormatter=this.attrs_.axes.x.valueFormatter}var n,r,o=[],s={},l=!1;for(n=1;n<e;n++){var h=t.getColumnType(n);if("number"==h)o.push(n);else{if("string"!=h||!this.getBooleanOption("displayAnnotations"))throw new Error("Only 'number' is supported as a dependent type with Gviz. 'string' is only supported if displayAnnotations is true");var u=o[o.length-1];s.hasOwnProperty(u)?s[u].push(n):s[u]=[n],l=!0}}var d=[t.getColumnLabel(0)];for(n=0;n<o.length;n++)d.push(t.getColumnLabel(o[n])),this.getBooleanOption("errorBars")&&(n+=1);this.attrs_.labels=d,e=d.length;var c=[],p=!1,g=[];for(n=0;n<a;n++){var f=[];if(void 0!==t.getValue(n,0)&&null!==t.getValue(n,0)){if("date"==i||"datetime"==i?f.push(t.getValue(n,0).getTime()):f.push(t.getValue(n,0)),this.getBooleanOption("errorBars"))for(r=0;r<e-1;r++)f.push([t.getValue(n,1+2*r),t.getValue(n,2+2*r)]);else{for(r=0;r<o.length;r++){var _=o[r];if(f.push(t.getValue(n,_)),l&&s.hasOwnProperty(_)&&null!==t.getValue(n,s[_][0])){var y={};y.series=t.getColumnLabel(_),y.xval=f[0],y.shortText=function(t){var e=String.fromCharCode(65+t%26);for(t=Math.floor(t/26);t>0;)e=String.fromCharCode(65+(t-1)%26)+e.toLowerCase(),t=Math.floor((t-1)/26);return e}(g.length),y.text="";for(var m=0;m<s[_].length;m++)m&&(y.text+="\n"),y.text+=t.getValue(n,s[_][m]);g.push(y)}}for(r=0;r<f.length;r++)isFinite(f[r])||(f[r]=null)}c.length>0&&f[0]<c[c.length-1][0]&&(p=!0),c.push(f)}else console.warn("Ignoring row "+n+" of DataTable because of undefined or null first column.")}p&&(console.warn("DataTable is out of order; order it correctly to speed loading."),c.sort(function(t,e){return t[0]-e[0]})),this.rawData_=c,g.length>0&&this.setAnnotations(g,!0),this.attributes_.reparseSeries()},Q.prototype.cascadeDataDidUpdateEvent_=function(){this.cascadeEvents_("dataDidUpdate",{})},Q.prototype.start_=function(){var t=this.file_;if("function"==typeof t&&(t=t()),x.isArrayLike(t))this.rawData_=this.parseArray_(t),this.cascadeDataDidUpdateEvent_(),this.predraw_();else if("object"==typeof t&&"function"==typeof t.getColumnRange)this.parseDataTable_(t),this.cascadeDataDidUpdateEvent_(),this.predraw_();else if("string"==typeof t){var e=x.detectLineDelimiter(t);if(e)this.loadedEvent_(t);else{var a;a=window.XMLHttpRequest?new XMLHttpRequest:new ActiveXObject("Microsoft.XMLHTTP");var i=this;a.onreadystatechange=function(){4==a.readyState&&(200!==a.status&&0!==a.status||i.loadedEvent_(a.responseText))},a.open("GET",t,!0),a.send(null)}}else console.error("Unknown data format: "+typeof t)},Q.prototype.updateOptions=function(t,e){void 0===e&&(e=!1);var a=t.file,i=Q.copyUserAttrs_(t);"rollPeriod"in i&&(this.rollPeriod_=i.rollPeriod),"dateWindow"in i&&(this.dateWindow_=i.dateWindow);var n=x.isPixelChangingOptionList(this.attr_("labels"),i);x.updateDeep(this.user_attrs_,i),this.attributes_.reparseSeries(),a?(this.cascadeEvents_("dataWillUpdate",{}),this.file_=a,e||this.start_()):e||(n?this.predraw_():this.renderGraph_(!1))},Q.copyUserAttrs_=function(t){var e={};for(var a in t)t.hasOwnProperty(a)&&"file"!=a&&t.hasOwnProperty(a)&&(e[a]=t[a]);return e},Q.prototype.resize=function(t,e){if(!this.resize_lock){this.resize_lock=!0,null===t!=(null===e)&&(console.warn("Dygraph.resize() should be called with zero parameters or two non-NULL parameters. Pretending it was zero."),t=e=null);var a=this.width_,i=this.height_;t?(this.maindiv_.style.width=t+"px",this.maindiv_.style.height=e+"px",this.width_=t,this.height_=e):(this.width_=this.maindiv_.clientWidth,this.height_=this.maindiv_.clientHeight),a==this.width_&&i==this.height_||(this.resizeElements_(),this.predraw_()),this.resize_lock=!1}},Q.prototype.adjustRoll=function(t){this.rollPeriod_=t,this.predraw_()},Q.prototype.visibility=function(){for(this.getOption("visibility")||(this.attrs_.visibility=[]);this.getOption("visibility").length<this.numColumns()-1;)this.attrs_.visibility.push(!0);return this.getOption("visibility")},Q.prototype.setVisibility=function(t,e){var a=this.visibility(),i=!1;if(Array.isArray(t)||(null!==t&&"object"==typeof t?i=!0:t=[t]),i)for(var n in t)t.hasOwnProperty(n)&&(n<0||n>=a.length?console.warn("Invalid series number in setVisibility: "+n):a[n]=t[n]);else for(var n=0;n<t.length;n++)"boolean"==typeof t[n]?n>=a.length?console.warn("Invalid series number in setVisibility: "+n):a[n]=t[n]:t[n]<0||t[n]>=a.length?console.warn("Invalid series number in setVisibility: "+t[n]):a[t[n]]=e;this.predraw_()},Q.prototype.size=function(){return{width:this.width_,height:this.height_}},Q.prototype.setAnnotations=function(t,e){if(this.annotations_=t,!this.layout_)return void console.warn("Tried to setAnnotations before dygraph was ready. Try setting them in a ready() block. See dygraphs.com/tests/annotation.html");this.layout_.setAnnotations(this.annotations_),e||this.predraw_()},Q.prototype.annotations=function(){return this.annotations_},Q.prototype.getLabels=function(){var t=this.attr_("labels");return t?t.slice():null},Q.prototype.indexFromSetName=function(t){return this.setIndexByName_[t]},Q.prototype.getRowForX=function(t){for(var e=0,a=this.numRows()-1;e<=a;){var i=a+e>>1,n=this.getValue(i,0);if(n<t)e=i+1;else if(n>t)a=i-1;else{if(e==i)return i;a=i}}return null},Q.prototype.ready=function(t){this.is_initial_draw_?this.readyFns_.push(t):t.call(this,this)},Q.prototype.addAndTrackEvent=function(t,e,a){x.addEvent(t,e,a),this.registeredEvents_.push({elem:t,type:e,fn:a})},Q.prototype.removeTrackedEvents_=function(){if(this.registeredEvents_)for(var t=0;t<this.registeredEvents_.length;t++){var e=this.registeredEvents_[t];x.removeEvent(e.elem,e.type,e.fn)}this.registeredEvents_=[]},Q.PLUGINS=[U.default,X.default,j.default,Z.default,H.default,G.default],Q.GVizChart=q.default,Q.DASHED_LINE=x.DASHED_LINE,Q.DOT_DASH_LINE=x.DOT_DASH_LINE,Q.dateAxisLabelFormatter=x.dateAxisLabelFormatter,Q.toRGB_=x.toRGB_,Q.findPos=x.findPos,Q.pageX=x.pageX,Q.pageY=x.pageY,Q.dateString_=x.dateString_,Q.defaultInteractionModel=f.default.defaultModel,Q.nonInteractiveModel=Q.nonInteractiveModel_=f.default.nonInteractiveModel_,Q.Circles=x.Circles,Q.Plugins={Legend:U.default,Axes:X.default,Annotations:H.default,ChartLabels:Z.default,Grid:G.default,RangeSelector:j.default},Q.DataHandlers={DefaultHandler:E.default,BarsHandler:R.default,CustomBarsHandler:P.default,DefaultFractionHandler:M.default,ErrorBarsHandler:T.default,FractionsBarsHandler:F.default},Q.startPan=f.default.startPan,Q.startZoom=f.default.startZoom,Q.movePan=f.default.movePan,Q.moveZoom=f.default.moveZoom,Q.endPan=f.default.endPan,Q.endZoom=f.default.endZoom,Q.numericLinearTicks=v.numericLinearTicks,Q.numericTicks=v.numericTicks,Q.dateTicker=v.dateTicker,Q.Granularity=v.Granularity,Q.getDateAxis=v.getDateAxis,Q.floatFormat=x.floatFormat,a.default=Q,e.exports=a.default}).call(this,t("_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(t,e,a){"use strict";function i(){this.tarps=[]}Object.defineProperty(a,"__esModule",{value:!0});var n=t("./dygraph-utils"),r=function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e.default=t,e}(n);i.prototype.cover=function(){for(var t=document.getElementsByTagName("iframe"),e=0;e<t.length;e++){var a=t[e],i=r.findPos(a),n=i.x,o=i.y,s=a.offsetWidth,l=a.offsetHeight,h=document.createElement("div");h.style.position="absolute",h.style.left=n+"px",h.style.top=o+"px",h.style.width=s+"px",h.style.height=l+"px",h.style.zIndex=999,document.body.appendChild(h),this.tarps.push(h)}},i.prototype.uncover=function(){for(var t=0;t<this.tarps.length;t++)this.tarps[t].parentNode.removeChild(this.tarps[t]);this.tarps=[]},a.default=i,e.exports=a.default},{"./dygraph-utils":17}],20:[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=function(){this.annotations_=[]};i.prototype.toString=function(){return"Annotations Plugin"},i.prototype.activate=function(t){return{clearChart:this.clearChart,didDrawChart:this.didDrawChart}},i.prototype.detachLabels=function(){for(var t=0;t<this.annotations_.length;t++){var e=this.annotations_[t];e.parentNode&&e.parentNode.removeChild(e),this.annotations_[t]=null}this.annotations_=[]},i.prototype.clearChart=function(t){this.detachLabels()},i.prototype.didDrawChart=function(t){var e=t.dygraph,a=e.layout_.annotated_points;if(a&&0!==a.length)for(var i=t.canvas.parentNode,n=function(t,a,i){return function(n){var r=i.annotation;r.hasOwnProperty(t)?r[t](r,i,e,n):e.getOption(a)&&e.getOption(a)(r,i,e,n)}},r=t.dygraph.getArea(),o={},s=0;s<a.length;s++){var l=a[s];if(!(l.canvasx<r.x||l.canvasx>r.x+r.w||l.canvasy<r.y||l.canvasy>r.y+r.h)){var h=l.annotation,u=6;h.hasOwnProperty("tickHeight")&&(u=h.tickHeight);var d=document.createElement("div")
+;d.style.fontSize=e.getOption("axisLabelFontSize")+"px";var c="dygraph-annotation";h.hasOwnProperty("icon")||(c+=" dygraphDefaultAnnotation dygraph-default-annotation"),h.hasOwnProperty("cssClass")&&(c+=" "+h.cssClass),d.className=c;var p=h.hasOwnProperty("width")?h.width:16,g=h.hasOwnProperty("height")?h.height:16;if(h.hasOwnProperty("icon")){var f=document.createElement("img");f.src=h.icon,f.width=p,f.height=g,d.appendChild(f)}else l.annotation.hasOwnProperty("shortText")&&d.appendChild(document.createTextNode(l.annotation.shortText));var _=l.canvasx-p/2;d.style.left=_+"px";var v=0;if(h.attachAtBottom){var y=r.y+r.h-g-u;o[_]?y-=o[_]:o[_]=0,o[_]+=u+g,v=y}else v=l.canvasy-g-u;d.style.top=v+"px",d.style.width=p+"px",d.style.height=g+"px",d.title=l.annotation.text,d.style.color=e.colorsMap_[l.name],d.style.borderColor=e.colorsMap_[l.name],h.div=d,e.addAndTrackEvent(d,"click",n("clickHandler","annotationClickHandler",l)),e.addAndTrackEvent(d,"mouseover",n("mouseOverHandler","annotationMouseOverHandler",l)),e.addAndTrackEvent(d,"mouseout",n("mouseOutHandler","annotationMouseOutHandler",l)),e.addAndTrackEvent(d,"dblclick",n("dblClickHandler","annotationDblClickHandler",l)),i.appendChild(d),this.annotations_.push(d);var x=t.drawingContext;if(x.save(),x.strokeStyle=h.hasOwnProperty("tickColor")?h.tickColor:e.colorsMap_[l.name],x.lineWidth=h.hasOwnProperty("tickWidth")?h.tickWidth:e.getOption("strokeWidth"),x.beginPath(),h.attachAtBottom){var y=v+g;x.moveTo(l.canvasx,y),x.lineTo(l.canvasx,y+u)}else x.moveTo(l.canvasx,l.canvasy),x.lineTo(l.canvasx,l.canvasy-2-u);x.closePath(),x.stroke(),x.restore()}}},i.prototype.destroy=function(){this.detachLabels()},a.default=i,e.exports=a.default},{}],21:[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=t("../dygraph-utils"),n=function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e.default=t,e}(i),r=function(){this.xlabels_=[],this.ylabels_=[]};r.prototype.toString=function(){return"Axes Plugin"},r.prototype.activate=function(t){return{layout:this.layout,clearChart:this.clearChart,willDrawChart:this.willDrawChart}},r.prototype.layout=function(t){var e=t.dygraph;if(e.getOptionForAxis("drawAxis","y")){var a=e.getOptionForAxis("axisLabelWidth","y")+2*e.getOptionForAxis("axisTickSize","y");t.reserveSpaceLeft(a)}if(e.getOptionForAxis("drawAxis","x")){var i;i=e.getOption("xAxisHeight")?e.getOption("xAxisHeight"):e.getOptionForAxis("axisLabelFontSize","x")+2*e.getOptionForAxis("axisTickSize","x"),t.reserveSpaceBottom(i)}if(2==e.numAxes()){if(e.getOptionForAxis("drawAxis","y2")){var a=e.getOptionForAxis("axisLabelWidth","y2")+2*e.getOptionForAxis("axisTickSize","y2");t.reserveSpaceRight(a)}}else e.numAxes()>2&&e.error("Only two y-axes are supported at this time. (Trying to use "+e.numAxes()+")")},r.prototype.detachLabels=function(){function t(t){for(var e=0;e<t.length;e++){var a=t[e];a.parentNode&&a.parentNode.removeChild(a)}}t(this.xlabels_),t(this.ylabels_),this.xlabels_=[],this.ylabels_=[]},r.prototype.clearChart=function(t){this.detachLabels()},r.prototype.willDrawChart=function(t){function e(t){return Math.round(t)+.5}function a(t){return Math.round(t)-.5}var i=this,r=t.dygraph;if(r.getOptionForAxis("drawAxis","x")||r.getOptionForAxis("drawAxis","y")||r.getOptionForAxis("drawAxis","y2")){var o,s,l,h=t.drawingContext,u=t.canvas.parentNode,d=r.width_,c=r.height_,p=function(t){return{position:"absolute",fontSize:r.getOptionForAxis("axisLabelFontSize",t)+"px",width:r.getOptionForAxis("axisLabelWidth",t)+"px"}},g={x:p("x"),y:p("y"),y2:p("y2")},f=function(t,e,a){var i=document.createElement("div"),r=g["y2"==a?"y2":e];n.update(i.style,r);var o=document.createElement("div");return o.className="dygraph-axis-label dygraph-axis-label-"+e+(a?" dygraph-axis-label-"+a:""),o.innerHTML=t,i.appendChild(o),i};h.save();var _=r.layout_,v=t.dygraph.plotter_.area,y=function(t){return function(e){return r.getOptionForAxis(e,t)}};if(r.getOptionForAxis("drawAxis","y")){if(_.yticks&&_.yticks.length>0){var x=r.numAxes(),m=[y("y"),y("y2")];_.yticks.forEach(function(t){if(void 0!==t.label){s=v.x;var e="y1",a=m[0];1==t.axis&&(s=v.x+v.w,-1,e="y2",a=m[1]);var n=a("axisLabelFontSize");l=v.y+t.pos*v.h,o=f(t.label,"y",2==x?e:null);var r=l-n/2;r<0&&(r=0),r+n+3>c?o.style.bottom="0":o.style.top=r+"px",0===t.axis?(o.style.left=v.x-a("axisLabelWidth")-a("axisTickSize")+"px",o.style.textAlign="right"):1==t.axis&&(o.style.left=v.x+v.w+a("axisTickSize")+"px",o.style.textAlign="left"),o.style.width=a("axisLabelWidth")+"px",u.appendChild(o),i.ylabels_.push(o)}});var b=this.ylabels_[0],w=r.getOptionForAxis("axisLabelFontSize","y");parseInt(b.style.top,10)+w>c-w&&(b.style.top=parseInt(b.style.top,10)-w/2+"px")}var A;if(r.getOption("drawAxesAtZero")){var O=r.toPercentXCoord(0);(O>1||O<0||isNaN(O))&&(O=0),A=e(v.x+O*v.w)}else A=e(v.x);h.strokeStyle=r.getOptionForAxis("axisLineColor","y"),h.lineWidth=r.getOptionForAxis("axisLineWidth","y"),h.beginPath(),h.moveTo(A,a(v.y)),h.lineTo(A,a(v.y+v.h)),h.closePath(),h.stroke(),2==r.numAxes()&&(h.strokeStyle=r.getOptionForAxis("axisLineColor","y2"),h.lineWidth=r.getOptionForAxis("axisLineWidth","y2"),h.beginPath(),h.moveTo(a(v.x+v.w),a(v.y)),h.lineTo(a(v.x+v.w),a(v.y+v.h)),h.closePath(),h.stroke())}if(r.getOptionForAxis("drawAxis","x")){if(_.xticks){var D=y("x");_.xticks.forEach(function(t){if(void 0!==t.label){s=v.x+t.pos*v.w,l=v.y+v.h,o=f(t.label,"x"),o.style.textAlign="center",o.style.top=l+D("axisTickSize")+"px";var e=s-D("axisLabelWidth")/2;e+D("axisLabelWidth")>d&&(e=d-D("axisLabelWidth"),o.style.textAlign="right"),e<0&&(e=0,o.style.textAlign="left"),o.style.left=e+"px",o.style.width=D("axisLabelWidth")+"px",u.appendChild(o),i.xlabels_.push(o)}})}h.strokeStyle=r.getOptionForAxis("axisLineColor","x"),h.lineWidth=r.getOptionForAxis("axisLineWidth","x"),h.beginPath();var E;if(r.getOption("drawAxesAtZero")){var O=r.toPercentYCoord(0,0);(O>1||O<0)&&(O=1),E=a(v.y+O*v.h)}else E=a(v.y+v.h);h.moveTo(e(v.x),E),h.lineTo(e(v.x+v.w),E),h.closePath(),h.stroke()}h.restore()}},a.default=r,e.exports=a.default},{"../dygraph-utils":17}],22:[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=function(){this.title_div_=null,this.xlabel_div_=null,this.ylabel_div_=null,this.y2label_div_=null};i.prototype.toString=function(){return"ChartLabels Plugin"},i.prototype.activate=function(t){return{layout:this.layout,didDrawChart:this.didDrawChart}};var n=function(t){var e=document.createElement("div");return e.style.position="absolute",e.style.left=t.x+"px",e.style.top=t.y+"px",e.style.width=t.w+"px",e.style.height=t.h+"px",e};i.prototype.detachLabels_=function(){for(var t=[this.title_div_,this.xlabel_div_,this.ylabel_div_,this.y2label_div_],e=0;e<t.length;e++){var a=t[e];a&&(a.parentNode&&a.parentNode.removeChild(a))}this.title_div_=null,this.xlabel_div_=null,this.ylabel_div_=null,this.y2label_div_=null};var r=function(t,e,a,i,n){var r=document.createElement("div");r.style.position="absolute",r.style.left=1==a?"0px":e.x+"px",r.style.top=e.y+"px",r.style.width=e.w+"px",r.style.height=e.h+"px",r.style.fontSize=t.getOption("yLabelWidth")-2+"px";var o=document.createElement("div");o.style.position="absolute",o.style.width=e.h+"px",o.style.height=e.w+"px",o.style.top=e.h/2-e.w/2+"px",o.style.left=e.w/2-e.h/2+"px",o.className="dygraph-label-rotate-"+(1==a?"right":"left");var s=document.createElement("div");return s.className=i,s.innerHTML=n,o.appendChild(s),r.appendChild(o),r};i.prototype.layout=function(t){this.detachLabels_();var e=t.dygraph,a=t.chart_div;if(e.getOption("title")){var i=t.reserveSpaceTop(e.getOption("titleHeight"));this.title_div_=n(i),this.title_div_.style.fontSize=e.getOption("titleHeight")-8+"px";var o=document.createElement("div");o.className="dygraph-label dygraph-title",o.innerHTML=e.getOption("title"),this.title_div_.appendChild(o),a.appendChild(this.title_div_)}if(e.getOption("xlabel")){var s=t.reserveSpaceBottom(e.getOption("xLabelHeight"));this.xlabel_div_=n(s),this.xlabel_div_.style.fontSize=e.getOption("xLabelHeight")-2+"px";var o=document.createElement("div");o.className="dygraph-label dygraph-xlabel",o.innerHTML=e.getOption("xlabel"),this.xlabel_div_.appendChild(o),a.appendChild(this.xlabel_div_)}if(e.getOption("ylabel")){var l=t.reserveSpaceLeft(0);this.ylabel_div_=r(e,l,1,"dygraph-label dygraph-ylabel",e.getOption("ylabel")),a.appendChild(this.ylabel_div_)}if(e.getOption("y2label")&&2==e.numAxes()){var h=t.reserveSpaceRight(0);this.y2label_div_=r(e,h,2,"dygraph-label dygraph-y2label",e.getOption("y2label")),a.appendChild(this.y2label_div_)}},i.prototype.didDrawChart=function(t){var e=t.dygraph;this.title_div_&&(this.title_div_.children[0].innerHTML=e.getOption("title")),this.xlabel_div_&&(this.xlabel_div_.children[0].innerHTML=e.getOption("xlabel")),this.ylabel_div_&&(this.ylabel_div_.children[0].children[0].innerHTML=e.getOption("ylabel")),this.y2label_div_&&(this.y2label_div_.children[0].children[0].innerHTML=e.getOption("y2label"))},i.prototype.clearChart=function(){},i.prototype.destroy=function(){this.detachLabels_()},a.default=i,e.exports=a.default},{}],23:[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=function(){};i.prototype.toString=function(){return"Gridline Plugin"},i.prototype.activate=function(t){return{willDrawChart:this.willDrawChart}},i.prototype.willDrawChart=function(t){function e(t){return Math.round(t)+.5}function a(t){return Math.round(t)-.5}var i,n,r,o,s=t.dygraph,l=t.drawingContext,h=s.layout_,u=t.dygraph.plotter_.area;if(s.getOptionForAxis("drawGrid","y")){for(var d=["y","y2"],c=[],p=[],g=[],f=[],_=[],r=0;r<d.length;r++)g[r]=s.getOptionForAxis("drawGrid",d[r]),g[r]&&(c[r]=s.getOptionForAxis("gridLineColor",d[r]),p[r]=s.getOptionForAxis("gridLineWidth",d[r]),_[r]=s.getOptionForAxis("gridLinePattern",d[r]),f[r]=_[r]&&_[r].length>=2);o=h.yticks,l.save(),o.forEach(function(t){if(t.has_tick){var r=t.axis;g[r]&&(l.save(),f[r]&&l.setLineDash&&l.setLineDash(_[r]),l.strokeStyle=c[r],l.lineWidth=p[r],i=e(u.x),n=a(u.y+t.pos*u.h),l.beginPath(),l.moveTo(i,n),l.lineTo(i+u.w,n),l.stroke(),l.restore())}}),l.restore()}if(s.getOptionForAxis("drawGrid","x")){o=h.xticks,l.save();var _=s.getOptionForAxis("gridLinePattern","x"),f=_&&_.length>=2;f&&l.setLineDash&&l.setLineDash(_),l.strokeStyle=s.getOptionForAxis("gridLineColor","x"),l.lineWidth=s.getOptionForAxis("gridLineWidth","x"),o.forEach(function(t){t.has_tick&&(i=e(u.x+t.pos*u.w),n=a(u.y+u.h),l.beginPath(),l.moveTo(i,n),l.lineTo(i,u.y),l.closePath(),l.stroke())}),f&&l.setLineDash&&l.setLineDash([]),l.restore()}},i.prototype.destroy=function(){},a.default=i,e.exports=a.default},{}],24:[function(t,e,a){"use strict";function i(t,e,a){if(!t||t.length<=1)return'<div class="dygraph-legend-line" style="border-bottom-color: '+e+';"></div>';var i,n,r,o,s,l=0,h=0,u=[];for(i=0;i<=t.length;i++)l+=t[i%t.length];if((s=Math.floor(a/(l-t[0])))>1){for(i=0;i<t.length;i++)u[i]=t[i]/a;h=u.length}else{for(s=1,i=0;i<t.length;i++)u[i]=t[i]/l;h=u.length+1}var d="";for(n=0;n<s;n++)for(i=0;i<h;i+=2)r=u[i%u.length],o=i<t.length?u[(i+1)%u.length]:0,d+='<div class="dygraph-legend-dash" style="margin-right: '+o+"em; padding-left: "+r+'em;"></div>';return d}Object.defineProperty(a,"__esModule",{value:!0});var n=t("../dygraph-utils"),r=function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e.default=t,e}(n),o=function(){this.legend_div_=null,this.is_generated_div_=!1};o.prototype.toString=function(){return"Legend Plugin"},o.prototype.activate=function(t){var e,a=t.getOption("labelsDiv");return a&&null!==a?e="string"==typeof a||a instanceof String?document.getElementById(a):a:(e=document.createElement("div"),e.className="dygraph-legend",t.graphDiv.appendChild(e),this.is_generated_div_=!0),this.legend_div_=e,this.one_em_width_=10,{select:this.select,deselect:this.deselect,predraw:this.predraw,didDrawChart:this.didDrawChart}};var s=function(t){var e=document.createElement("span");e.setAttribute("style","margin: 0; padding: 0 0 0 1em; border: 0;"),t.appendChild(e);var a=e.offsetWidth;return t.removeChild(e),a},l=function(t){return t.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")};o.prototype.select=function(t){var e=t.selectedX,a=t.selectedPoints,i=t.selectedRow,n=t.dygraph.getOption("legend");if("never"===n)return void(this.legend_div_.style.display="none");if("follow"===n){var r=t.dygraph.plotter_.area,s=this.legend_div_.offsetWidth,l=t.dygraph.getOptionForAxis("axisLabelWidth","y"),h=a[0].x*r.w+50,u=a[0].y*r.h-50;h+s+1>r.w&&(h=h-100-s-(l-r.x)),t.dygraph.graphDiv.appendChild(this.legend_div_),this.legend_div_.style.left=l+h+"px",this.legend_div_.style.top=u+"px"}var d=o.generateLegendHTML(t.dygraph,e,a,this.one_em_width_,i);this.legend_div_.innerHTML=d,this.legend_div_.style.display=""},o.prototype.deselect=function(t){"always"!==t.dygraph.getOption("legend")&&(this.legend_div_.style.display="none");var e=s(this.legend_div_);this.one_em_width_=e;var a=o.generateLegendHTML(t.dygraph,void 0,void 0,e,null);this.legend_div_.innerHTML=a},o.prototype.didDrawChart=function(t){this.deselect(t)},o.prototype.predraw=function(t){if(this.is_generated_div_){t.dygraph.graphDiv.appendChild(this.legend_div_);var e=t.dygraph.getArea(),a=this.legend_div_.offsetWidth;this.legend_div_.style.left=e.x+e.w-a-1+"px",this.legend_div_.style.top=e.y+"px"}},o.prototype.destroy=function(){this.legend_div_=null},o.generateLegendHTML=function(t,e,a,n,s){var h={dygraph:t,x:e,series:[]},u={},d=t.getLabels();if(d)for(var c=1;c<d.length;c++){var p=t.getPropertiesForSeries(d[c]),g=t.getOption("strokePattern",d[c]),f={dashHTML:i(g,p.color,n),label:d[c],labelHTML:l(d[c]),isVisible:p.visible,color:p.color};h.series.push(f),u[d[c]]=f}if(void 0!==e){var _=t.optionsViewForAxis_("x"),v=_("valueFormatter");h.xHTML=v.call(t,e,_,d[0],t,s,0);for(var y=[],x=t.numAxes(),c=0;c<x;c++)y[c]=t.optionsViewForAxis_("y"+(c?1+c:""));var m=t.getOption("labelsShowZeroValues"),b=t.getHighlightSeries();for(c=0;c<a.length;c++){var w=a[c],f=u[w.name];if(f.y=w.yval,0===w.yval&&!m||isNaN(w.canvasy))f.isVisible=!1;else{var p=t.getPropertiesForSeries(w.name),A=y[p.axis-1],O=A("valueFormatter"),D=O.call(t,w.yval,A,w.name,t,s,d.indexOf(w.name));r.update(f,{yHTML:D}),w.name==b&&(f.isHighlighted=!0)}}}return(t.getOption("legendFormatter")||o.defaultFormatter).call(t,h)},o.defaultFormatter=function(t){var e=t.dygraph;if(!0!==e.getOption("showLabelsOnHighlight"))return"";var a,i=e.getOption("labelsSeparateLines");if(void 0===t.x){if("always"!=e.getOption("legend"))return"";a="";for(var n=0;n<t.series.length;n++){var r=t.series[n];r.isVisible&&(""!==a&&(a+=i?"<br/>":" "),a+="<span style='font-weight: bold; color: "+r.color+";'>"+r.dashHTML+" "+r.labelHTML+"</span>")}return a}a=t.xHTML+":";for(var n=0;n<t.series.length;n++){var r=t.series[n];if(r.isVisible){i&&(a+="<br>");a+="<span"+(r.isHighlighted?' class="highlight"':"")+"> <b><span style='color: "+r.color+";'>"+r.labelHTML+"</span></b>:&#160;"+r.yHTML+"</span>"}}return a},a.default=o,e.exports=a.default},{"../dygraph-utils":17}],25:[function(t,e,a){"use strict";function i(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(a,"__esModule",{value:!0});var n=t("../dygraph-utils"),r=function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e.default=t,e}(n),o=t("../dygraph-interaction-model"),s=i(o),l=t("../iframe-tarp"),h=i(l),u=function(){this.hasTouchInterface_="undefined"!=typeof TouchEvent,this.isMobileDevice_=/mobile|android/gi.test(navigator.appVersion),this.interfaceCreated_=!1};u.prototype.toString=function(){return"RangeSelector Plugin"},u.prototype.activate=function(t){return this.dygraph_=t,this.getOption_("showRangeSelector")&&this.createInterface_(),{layout:this.reserveSpace_,predraw:this.renderStaticLayer_,didDrawChart:this.renderInteractiveLayer_}},u.prototype.destroy=function(){this.bgcanvas_=null,this.fgcanvas_=null,this.leftZoomHandle_=null,this.rightZoomHandle_=null},u.prototype.getOption_=function(t,e){return this.dygraph_.getOption(t,e)},u.prototype.setDefaultOption_=function(t,e){this.dygraph_.attrs_[t]=e},u.prototype.createInterface_=function(){this.createCanvases_(),this.createZoomHandles_(),this.initInteraction_(),this.getOption_("animatedZooms")&&(console.warn("Animated zooms and range selector are not compatible; disabling animatedZooms."),this.dygraph_.updateOptions({animatedZooms:!1},!0)),this.interfaceCreated_=!0,this.addToGraph_()},u.prototype.addToGraph_=function(){var t=this.graphDiv_=this.dygraph_.graphDiv;t.appendChild(this.bgcanvas_),t.appendChild(this.fgcanvas_),t.appendChild(this.leftZoomHandle_),t.appendChild(this.rightZoomHandle_)},u.prototype.removeFromGraph_=function(){var t=this.graphDiv_;t.removeChild(this.bgcanvas_),t.removeChild(this.fgcanvas_),t.removeChild(this.leftZoomHandle_),t.removeChild(this.rightZoomHandle_),this.graphDiv_=null},u.prototype.reserveSpace_=function(t){this.getOption_("showRangeSelector")&&t.reserveSpaceBottom(this.getOption_("rangeSelectorHeight")+4)},u.prototype.renderStaticLayer_=function(){this.updateVisibility_()&&(this.resize_(),this.drawStaticLayer_())},u.prototype.renderInteractiveLayer_=function(){this.updateVisibility_()&&!this.isChangingRange_&&(this.placeZoomHandles_(),this.drawInteractiveLayer_())},u.prototype.updateVisibility_=function(){var t=this.getOption_("showRangeSelector");if(t)this.interfaceCreated_?this.graphDiv_&&this.graphDiv_.parentNode||this.addToGraph_():this.createInterface_();else if(this.graphDiv_){this.removeFromGraph_();var e=this.dygraph_;setTimeout(function(){e.width_=0,e.resize()},1)}return t},u.prototype.resize_=function(){function t(t,e,a,i){var n=i||r.getContextPixelRatio(e);t.style.top=a.y+"px",t.style.left=a.x+"px",t.width=a.w*n,t.height=a.h*n,t.style.width=a.w+"px",t.style.height=a.h+"px",1!=n&&e.scale(n,n)}var e=this.dygraph_.layout_.getPlotArea(),a=0;this.dygraph_.getOptionForAxis("drawAxis","x")&&(a=this.getOption_("xAxisHeight")||this.getOption_("axisLabelFontSize")+2*this.getOption_("axisTickSize")),this.canvasRect_={x:e.x,y:e.y+e.h+a+4,w:e.w,h:this.getOption_("rangeSelectorHeight")};var i=this.dygraph_.getNumericOption("pixelRatio");t(this.bgcanvas_,this.bgcanvas_ctx_,this.canvasRect_,i),t(this.fgcanvas_,this.fgcanvas_ctx_,this.canvasRect_,i)},u.prototype.createCanvases_=function(){this.bgcanvas_=r.createCanvas(),this.bgcanvas_.className="dygraph-rangesel-bgcanvas",this.bgcanvas_.style.position="absolute",this.bgcanvas_.style.zIndex=9,this.bgcanvas_ctx_=r.getContext(this.bgcanvas_),this.fgcanvas_=r.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_=r.getContext(this.fgcanvas_)},u.prototype.createZoomHandles_=function(){var t=new Image;t.className="dygraph-rangesel-zoomhandle",t.style.position="absolute",t.style.zIndex=10,t.style.visibility="hidden",t.style.cursor="col-resize",t.width=9,t.height=16,t.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAAzwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7sqSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII=",this.isMobileDevice_&&(t.width*=2,t.height*=2),this.leftZoomHandle_=t,this.rightZoomHandle_=t.cloneNode(!1)},u.prototype.initInteraction_=function(){var t,e,a,i,n,o,l,u,d,c,p,g,f,_,v=this,y=document,x=0,m=null,b=!1,w=!1,A=!this.isMobileDevice_,O=new h.default;t=function(t){var e=v.dygraph_.xAxisExtremes(),a=(e[1]-e[0])/v.canvasRect_.w;return[e[0]+(t.leftHandlePos-v.canvasRect_.x)*a,e[0]+(t.rightHandlePos-v.canvasRect_.x)*a]},e=function(t){return r.cancelEvent(t),b=!0,x=t.clientX,m=t.target?t.target:t.srcElement,"mousedown"!==t.type&&"dragstart"!==t.type||(r.addEvent(y,"mousemove",a),r.addEvent(y,"mouseup",i)),v.fgcanvas_.style.cursor="col-resize",O.cover(),!0},a=function(t){if(!b)return!1;r.cancelEvent(t);var e=t.clientX-x;if(Math.abs(e)<4)return!0;x=t.clientX;var a,i=v.getZoomHandleStatus_();m==v.leftZoomHandle_?(a=i.leftHandlePos+e,a=Math.min(a,i.rightHandlePos-m.width-3),a=Math.max(a,v.canvasRect_.x)):(a=i.rightHandlePos+e,a=Math.min(a,v.canvasRect_.x+v.canvasRect_.w),a=Math.max(a,i.leftHandlePos+m.width+3));var o=m.width/2;return m.style.left=a-o+"px",v.drawInteractiveLayer_(),A&&n(),!0},i=function(t){return!!b&&(b=!1,O.uncover(),r.removeEvent(y,"mousemove",a),r.removeEvent(y,"mouseup",i),v.fgcanvas_.style.cursor="default",A||n(),!0)},n=function(){try{var e=v.getZoomHandleStatus_();if(v.isChangingRange_=!0,e.isZoomed){var a=t(e);v.dygraph_.doZoomXDates_(a[0],a[1])}else v.dygraph_.resetZoom()}finally{v.isChangingRange_=!1}},o=function(t){var e=v.leftZoomHandle_.getBoundingClientRect(),a=e.left+e.width/2;e=v.rightZoomHandle_.getBoundingClientRect();var i=e.left+e.width/2;return t.clientX>a&&t.clientX<i},l=function(t){return!(w||!o(t)||!v.getZoomHandleStatus_().isZoomed)&&(r.cancelEvent(t),w=!0,x=t.clientX,"mousedown"===t.type&&(r.addEvent(y,"mousemove",u),r.addEvent(y,"mouseup",d)),!0)},u=function(t){if(!w)return!1;r.cancelEvent(t);var e=t.clientX-x;if(Math.abs(e)<4)return!0;x=t.clientX;var a=v.getZoomHandleStatus_(),i=a.leftHandlePos,n=a.rightHandlePos,o=n-i;i+e<=v.canvasRect_.x?(i=v.canvasRect_.x,n=i+o):n+e>=v.canvasRect_.x+v.canvasRect_.w?(n=v.canvasRect_.x+v.canvasRect_.w,i=n-o):(i+=e,n+=e);var s=v.leftZoomHandle_.width/2;return v.leftZoomHandle_.style.left=i-s+"px",v.rightZoomHandle_.style.left=n-s+"px",v.drawInteractiveLayer_(),A&&c(),!0},d=function(t){return!!w&&(w=!1,r.removeEvent(y,"mousemove",u),r.removeEvent(y,"mouseup",d),A||c(),!0)},c=function(){try{v.isChangingRange_=!0,v.dygraph_.dateWindow_=t(v.getZoomHandleStatus_()),v.dygraph_.drawGraph_(!1)}finally{v.isChangingRange_=!1}},p=function(t){if(!b&&!w){var e=o(t)?"move":"default";e!=v.fgcanvas_.style.cursor&&(v.fgcanvas_.style.cursor=e)}},g=function(t){"touchstart"==t.type&&1==t.targetTouches.length?e(t.targetTouches[0])&&r.cancelEvent(t):"touchmove"==t.type&&1==t.targetTouches.length?a(t.targetTouches[0])&&r.cancelEvent(t):i(t)},f=function(t){"touchstart"==t.type&&1==t.targetTouches.length?l(t.targetTouches[0])&&r.cancelEvent(t):"touchmove"==t.type&&1==t.targetTouches.length?u(t.targetTouches[0])&&r.cancelEvent(t):d(t)},_=function(t,e){for(var a=["touchstart","touchend","touchmove","touchcancel"],i=0;i<a.length;i++)v.dygraph_.addAndTrackEvent(t,a[i],e)},this.setDefaultOption_("interactionModel",s.default.dragIsPanInteractionModel),this.setDefaultOption_("panEdgeFraction",1e-4);var D=window.opera?"mousedown":"dragstart";this.dygraph_.addAndTrackEvent(this.leftZoomHandle_,D,e),this.dygraph_.addAndTrackEvent(this.rightZoomHandle_,D,e),this.dygraph_.addAndTrackEvent(this.fgcanvas_,"mousedown",l),this.dygraph_.addAndTrackEvent(this.fgcanvas_,"mousemove",p),this.hasTouchInterface_&&(_(this.leftZoomHandle_,g),_(this.rightZoomHandle_,g),_(this.fgcanvas_,f))},u.prototype.drawStaticLayer_=function(){var t=this.bgcanvas_ctx_;t.clearRect(0,0,this.canvasRect_.w,this.canvasRect_.h);try{this.drawMiniPlot_()}catch(t){console.warn(t)}this.bgcanvas_ctx_.lineWidth=this.getOption_("rangeSelectorBackgroundLineWidth"),t.strokeStyle=this.getOption_("rangeSelectorBackgroundStrokeColor"),t.beginPath(),t.moveTo(.5,.5),t.lineTo(.5,this.canvasRect_.h-.5),t.lineTo(this.canvasRect_.w-.5,this.canvasRect_.h-.5),t.lineTo(this.canvasRect_.w-.5,.5),t.stroke()},u.prototype.drawMiniPlot_=function(){var t=this.getOption_("rangeSelectorPlotFillColor"),e=this.getOption_("rangeSelectorPlotFillGradientColor"),a=this.getOption_("rangeSelectorPlotStrokeColor");if(t||a){var i=this.getOption_("stepPlot"),n=this.computeCombinedSeriesAndLimits_(),r=n.yMax-n.yMin,o=this.bgcanvas_ctx_,s=this.dygraph_.xAxisExtremes(),l=Math.max(s[1]-s[0],1e-30),h=(this.canvasRect_.w-.5)/l,u=(this.canvasRect_.h-.5)/r,d=this.canvasRect_.w-.5,c=this.canvasRect_.h-.5,p=null,g=null;o.beginPath(),o.moveTo(.5,c);for(var f=0;f<n.data.length;f++){var _=n.data[f],v=null!==_[0]?(_[0]-s[0])*h:NaN,y=null!==_[1]?c-(_[1]-n.yMin)*u:NaN;(i||null===p||Math.round(v)!=Math.round(p))&&(isFinite(v)&&isFinite(y)?(null===p?o.lineTo(v,c):i&&o.lineTo(v,g),o.lineTo(v,y),p=v,g=y):(null!==p&&(i?(o.lineTo(v,g),o.lineTo(v,c)):o.lineTo(p,c)),p=g=null))}if(o.lineTo(d,c),o.closePath(),t){var x=this.bgcanvas_ctx_.createLinearGradient(0,0,0,c);e&&x.addColorStop(0,e),x.addColorStop(1,t),this.bgcanvas_ctx_.fillStyle=x,o.fill()}a&&(this.bgcanvas_ctx_.strokeStyle=a,this.bgcanvas_ctx_.lineWidth=this.getOption_("rangeSelectorPlotLineWidth"),o.stroke())}},u.prototype.computeCombinedSeriesAndLimits_=function(){var t,e=this.dygraph_,a=this.getOption_("logscale"),i=e.numColumns(),n=e.getLabels(),o=new Array(i),s=!1,l=e.visibility(),h=[];for(t=1;t<i;t++){var u=this.getOption_("showInRangeSelector",n[t]);h.push(u),null!==u&&(s=!0)}if(s)for(t=1;t<i;t++)o[t]=h[t-1];else for(t=1;t<i;t++)o[t]=l[t-1];var d=[],c=e.dataHandler_,p=e.attributes_;for(t=1;t<e.numColumns();t++)if(o[t]){var g=c.extractSeries(e.rawData_,t,p);e.rollPeriod()>1&&(g=c.rollingAverage(g,e.rollPeriod(),p)),d.push(g)}var f=[];for(t=0;t<d[0].length;t++){for(var _=0,v=0,y=0;y<d.length;y++){var x=d[y][t][1];null===x||isNaN(x)||(v++,_+=x)}f.push([d[0][t][0],_/v])}var m=Number.MAX_VALUE,b=-Number.MAX_VALUE;for(t=0;t<f.length;t++){var w=f[t][1];null!==w&&isFinite(w)&&(!a||w>0)&&(m=Math.min(m,w),b=Math.max(b,w))}if(a)for(b=r.log10(b),b+=.25*b,m=r.log10(m),t=0;t<f.length;t++)f[t][1]=r.log10(f[t][1]);else{var A,O=b-m;A=O<=Number.MIN_VALUE?.25*b:.25*O,b+=A,m-=A}return{data:f,yMin:m,yMax:b}},u.prototype.placeZoomHandles_=function(){var t=this.dygraph_.xAxisExtremes(),e=this.dygraph_.xAxisRange(),a=t[1]-t[0],i=Math.max(0,(e[0]-t[0])/a),n=Math.max(0,(t[1]-e[1])/a),r=this.canvasRect_.x+this.canvasRect_.w*i,o=this.canvasRect_.x+this.canvasRect_.w*(1-n),s=Math.max(this.canvasRect_.y,this.canvasRect_.y+(this.canvasRect_.h-this.leftZoomHandle_.height)/2),l=this.leftZoomHandle_.width/2;this.leftZoomHandle_.style.left=r-l+"px",this.leftZoomHandle_.style.top=s+"px",this.rightZoomHandle_.style.left=o-l+"px",this.rightZoomHandle_.style.top=this.leftZoomHandle_.style.top,this.leftZoomHandle_.style.visibility="visible",this.rightZoomHandle_.style.visibility="visible"},u.prototype.drawInteractiveLayer_=function(){var t=this.fgcanvas_ctx_;t.clearRect(0,0,this.canvasRect_.w,this.canvasRect_.h);var e=this.canvasRect_.w-1,a=this.canvasRect_.h-1,i=this.getZoomHandleStatus_();if(t.strokeStyle=this.getOption_("rangeSelectorForegroundStrokeColor"),t.lineWidth=this.getOption_("rangeSelectorForegroundLineWidth"),i.isZoomed){var n=Math.max(1,i.leftHandlePos-this.canvasRect_.x),r=Math.min(e,i.rightHandlePos-this.canvasRect_.x);t.fillStyle="rgba(240, 240, 240, "+this.getOption_("rangeSelectorAlpha").toString()+")",t.fillRect(0,0,n,this.canvasRect_.h),t.fillRect(r,0,this.canvasRect_.w-r,this.canvasRect_.h),t.beginPath(),t.moveTo(1,1),t.lineTo(n,1),t.lineTo(n,a),t.lineTo(r,a),t.lineTo(r,1),t.lineTo(e,1),t.stroke()}else t.beginPath(),t.moveTo(1,1),t.lineTo(1,a),t.lineTo(e,a),t.lineTo(e,1),t.stroke()},u.prototype.getZoomHandleStatus_=function(){var t=this.leftZoomHandle_.width/2,e=parseFloat(this.leftZoomHandle_.style.left)+t,a=parseFloat(this.rightZoomHandle_.style.left)+t;return{leftHandlePos:e,rightHandlePos:a,isZoomed:e-1>this.canvasRect_.x||a+1<this.canvasRect_.x+this.canvasRect_.w}},a.default=u,e.exports=a.default},{"../dygraph-interaction-model":12,"../dygraph-utils":17,"../iframe-tarp":19}]},{},[18])(18)});
+//# sourceMappingURL=dist/dygraph.min.js.map
diff --git a/modules/http/static/dygraph.min.js.spdx b/modules/http/static/dygraph.min.js.spdx
new file mode 100644
index 0000000..9b03d07
--- /dev/null
+++ b/modules/http/static/dygraph.min.js.spdx
@@ -0,0 +1,12 @@
+SPDXVersion: SPDX-2.1
+DataLicense: CC0-1.0
+SPDXID: SPDXRef-DOCUMENT
+DocumentName: dygraph
+DocumentNamespace: http://spdx.org/spdxdocs/spdx-v2.1-4433b214-29e7-41ba-afa9-5d06f5e643e1
+
+PackageName: dygraph
+PackageVersion: 2.1.0
+PackageDownloadLocation: http://dygraphs.com/2.1.0/dygraph.min.js
+PackageChecksum: SHA256: f3b2eafa9250129f3cadf4eef279dd3ede862d4f2fa193094ea7aff3bd231303
+PackageOriginator: Person: Dan Vanderkam (danvdk@gmail.com)
+PackageLicenseDeclared: MIT
diff --git a/modules/http/static/epoch.css b/modules/http/static/epoch.css
new file mode 100644
index 0000000..928f441
--- /dev/null
+++ b/modules/http/static/epoch.css
@@ -0,0 +1,2 @@
+/* SPDX-License-Identifier: MIT */
+.epoch .axis path,.epoch .axis line{shape-rendering:crispEdges}.epoch .axis.canvas .tick line{shape-rendering:geometricPrecision}div#_canvas_css_reference{width:0;height:0;position:absolute;top:-1000px;left:-1000px}div#_canvas_css_reference svg{position:absolute;width:0;height:0;top:-1000px;left:-1000px}.epoch{font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;font-size:12pt}.epoch .axis path,.epoch .axis line{fill:transparent;stroke:#000}.epoch .axis .tick text{font-size:9pt}.epoch .line{fill:transparent;stroke-width:2px}.epoch.sparklines .line{stroke-width:1px}.epoch .area{stroke:transparent}.epoch .arc.pie{stroke:#fff;stroke-width:1.5px}.epoch .arc.pie text{stroke:transparent;fill:white;font-size:9pt}.epoch .gauge-labels .value{text-anchor:middle;font-size:140%;fill:#666}.epoch.gauge-tiny{width:120px;height:90px}.epoch.gauge-tiny .gauge-labels .value{font-size:80%}.epoch.gauge-tiny .gauge .arc.outer{stroke-width:2px}.epoch.gauge-small{width:180px;height:135px}.epoch.gauge-small .gauge-labels .value{font-size:120%}.epoch.gauge-small .gauge .arc.outer{stroke-width:3px}.epoch.gauge-medium{width:240px;height:180px}.epoch.gauge-medium .gauge .arc.outer{stroke-width:3px}.epoch.gauge-large{width:320px;height:240px}.epoch.gauge-large .gauge-labels .value{font-size:180%}.epoch .gauge .arc.outer{stroke-width:4px;stroke:#666}.epoch .gauge .arc.inner{stroke-width:1px;stroke:#555}.epoch .gauge .tick{stroke-width:1px;stroke:#555}.epoch .gauge .needle{fill:orange}.epoch .gauge .needle-base{fill:#666}.epoch div.ref.category1,.epoch.category10 div.ref.category1{background-color:#1f77b4}.epoch .category1 .line,.epoch.category10 .category1 .line{stroke:#1f77b4}.epoch .category1 .area,.epoch .category1 .dot,.epoch.category10 .category1 .area,.epoch.category10 .category1 .dot{fill:#1f77b4;stroke:transparent}.epoch .arc.category1 path,.epoch.category10 .arc.category1 path{fill:#1f77b4}.epoch .bar.category1,.epoch.category10 .bar.category1{fill:#1f77b4}.epoch div.ref.category2,.epoch.category10 div.ref.category2{background-color:#ff7f0e}.epoch .category2 .line,.epoch.category10 .category2 .line{stroke:#ff7f0e}.epoch .category2 .area,.epoch .category2 .dot,.epoch.category10 .category2 .area,.epoch.category10 .category2 .dot{fill:#ff7f0e;stroke:transparent}.epoch .arc.category2 path,.epoch.category10 .arc.category2 path{fill:#ff7f0e}.epoch .bar.category2,.epoch.category10 .bar.category2{fill:#ff7f0e}.epoch div.ref.category3,.epoch.category10 div.ref.category3{background-color:#2ca02c}.epoch .category3 .line,.epoch.category10 .category3 .line{stroke:#2ca02c}.epoch .category3 .area,.epoch .category3 .dot,.epoch.category10 .category3 .area,.epoch.category10 .category3 .dot{fill:#2ca02c;stroke:transparent}.epoch .arc.category3 path,.epoch.category10 .arc.category3 path{fill:#2ca02c}.epoch .bar.category3,.epoch.category10 .bar.category3{fill:#2ca02c}.epoch div.ref.category4,.epoch.category10 div.ref.category4{background-color:#d62728}.epoch .category4 .line,.epoch.category10 .category4 .line{stroke:#d62728}.epoch .category4 .area,.epoch .category4 .dot,.epoch.category10 .category4 .area,.epoch.category10 .category4 .dot{fill:#d62728;stroke:transparent}.epoch .arc.category4 path,.epoch.category10 .arc.category4 path{fill:#d62728}.epoch .bar.category4,.epoch.category10 .bar.category4{fill:#d62728}.epoch div.ref.category5,.epoch.category10 div.ref.category5{background-color:#9467bd}.epoch .category5 .line,.epoch.category10 .category5 .line{stroke:#9467bd}.epoch .category5 .area,.epoch .category5 .dot,.epoch.category10 .category5 .area,.epoch.category10 .category5 .dot{fill:#9467bd;stroke:transparent}.epoch .arc.category5 path,.epoch.category10 .arc.category5 path{fill:#9467bd}.epoch .bar.category5,.epoch.category10 .bar.category5{fill:#9467bd}.epoch div.ref.category6,.epoch.category10 div.ref.category6{background-color:#8c564b}.epoch .category6 .line,.epoch.category10 .category6 .line{stroke:#8c564b}.epoch .category6 .area,.epoch .category6 .dot,.epoch.category10 .category6 .area,.epoch.category10 .category6 .dot{fill:#8c564b;stroke:transparent}.epoch .arc.category6 path,.epoch.category10 .arc.category6 path{fill:#8c564b}.epoch .bar.category6,.epoch.category10 .bar.category6{fill:#8c564b}.epoch div.ref.category7,.epoch.category10 div.ref.category7{background-color:#e377c2}.epoch .category7 .line,.epoch.category10 .category7 .line{stroke:#e377c2}.epoch .category7 .area,.epoch .category7 .dot,.epoch.category10 .category7 .area,.epoch.category10 .category7 .dot{fill:#e377c2;stroke:transparent}.epoch .arc.category7 path,.epoch.category10 .arc.category7 path{fill:#e377c2}.epoch .bar.category7,.epoch.category10 .bar.category7{fill:#e377c2}.epoch div.ref.category8,.epoch.category10 div.ref.category8{background-color:#7f7f7f}.epoch .category8 .line,.epoch.category10 .category8 .line{stroke:#7f7f7f}.epoch .category8 .area,.epoch .category8 .dot,.epoch.category10 .category8 .area,.epoch.category10 .category8 .dot{fill:#7f7f7f;stroke:transparent}.epoch .arc.category8 path,.epoch.category10 .arc.category8 path{fill:#7f7f7f}.epoch .bar.category8,.epoch.category10 .bar.category8{fill:#7f7f7f}.epoch div.ref.category9,.epoch.category10 div.ref.category9{background-color:#bcbd22}.epoch .category9 .line,.epoch.category10 .category9 .line{stroke:#bcbd22}.epoch .category9 .area,.epoch .category9 .dot,.epoch.category10 .category9 .area,.epoch.category10 .category9 .dot{fill:#bcbd22;stroke:transparent}.epoch .arc.category9 path,.epoch.category10 .arc.category9 path{fill:#bcbd22}.epoch .bar.category9,.epoch.category10 .bar.category9{fill:#bcbd22}.epoch div.ref.category10,.epoch.category10 div.ref.category10{background-color:#17becf}.epoch .category10 .line,.epoch.category10 .category10 .line{stroke:#17becf}.epoch .category10 .area,.epoch .category10 .dot,.epoch.category10 .category10 .area,.epoch.category10 .category10 .dot{fill:#17becf;stroke:transparent}.epoch .arc.category10 path,.epoch.category10 .arc.category10 path{fill:#17becf}.epoch .bar.category10,.epoch.category10 .bar.category10{fill:#17becf}.epoch.category20 div.ref.category1{background-color:#1f77b4}.epoch.category20 .category1 .line{stroke:#1f77b4}.epoch.category20 .category1 .area,.epoch.category20 .category1 .dot{fill:#1f77b4;stroke:transparent}.epoch.category20 .arc.category1 path{fill:#1f77b4}.epoch.category20 .bar.category1{fill:#1f77b4}.epoch.category20 div.ref.category2{background-color:#aec7e8}.epoch.category20 .category2 .line{stroke:#aec7e8}.epoch.category20 .category2 .area,.epoch.category20 .category2 .dot{fill:#aec7e8;stroke:transparent}.epoch.category20 .arc.category2 path{fill:#aec7e8}.epoch.category20 .bar.category2{fill:#aec7e8}.epoch.category20 div.ref.category3{background-color:#ff7f0e}.epoch.category20 .category3 .line{stroke:#ff7f0e}.epoch.category20 .category3 .area,.epoch.category20 .category3 .dot{fill:#ff7f0e;stroke:transparent}.epoch.category20 .arc.category3 path{fill:#ff7f0e}.epoch.category20 .bar.category3{fill:#ff7f0e}.epoch.category20 div.ref.category4{background-color:#ffbb78}.epoch.category20 .category4 .line{stroke:#ffbb78}.epoch.category20 .category4 .area,.epoch.category20 .category4 .dot{fill:#ffbb78;stroke:transparent}.epoch.category20 .arc.category4 path{fill:#ffbb78}.epoch.category20 .bar.category4{fill:#ffbb78}.epoch.category20 div.ref.category5{background-color:#2ca02c}.epoch.category20 .category5 .line{stroke:#2ca02c}.epoch.category20 .category5 .area,.epoch.category20 .category5 .dot{fill:#2ca02c;stroke:transparent}.epoch.category20 .arc.category5 path{fill:#2ca02c}.epoch.category20 .bar.category5{fill:#2ca02c}.epoch.category20 div.ref.category6{background-color:#98df8a}.epoch.category20 .category6 .line{stroke:#98df8a}.epoch.category20 .category6 .area,.epoch.category20 .category6 .dot{fill:#98df8a;stroke:transparent}.epoch.category20 .arc.category6 path{fill:#98df8a}.epoch.category20 .bar.category6{fill:#98df8a}.epoch.category20 div.ref.category7{background-color:#d62728}.epoch.category20 .category7 .line{stroke:#d62728}.epoch.category20 .category7 .area,.epoch.category20 .category7 .dot{fill:#d62728;stroke:transparent}.epoch.category20 .arc.category7 path{fill:#d62728}.epoch.category20 .bar.category7{fill:#d62728}.epoch.category20 div.ref.category8{background-color:#ff9896}.epoch.category20 .category8 .line{stroke:#ff9896}.epoch.category20 .category8 .area,.epoch.category20 .category8 .dot{fill:#ff9896;stroke:transparent}.epoch.category20 .arc.category8 path{fill:#ff9896}.epoch.category20 .bar.category8{fill:#ff9896}.epoch.category20 div.ref.category9{background-color:#9467bd}.epoch.category20 .category9 .line{stroke:#9467bd}.epoch.category20 .category9 .area,.epoch.category20 .category9 .dot{fill:#9467bd;stroke:transparent}.epoch.category20 .arc.category9 path{fill:#9467bd}.epoch.category20 .bar.category9{fill:#9467bd}.epoch.category20 div.ref.category10{background-color:#c5b0d5}.epoch.category20 .category10 .line{stroke:#c5b0d5}.epoch.category20 .category10 .area,.epoch.category20 .category10 .dot{fill:#c5b0d5;stroke:transparent}.epoch.category20 .arc.category10 path{fill:#c5b0d5}.epoch.category20 .bar.category10{fill:#c5b0d5}.epoch.category20 div.ref.category11{background-color:#8c564b}.epoch.category20 .category11 .line{stroke:#8c564b}.epoch.category20 .category11 .area,.epoch.category20 .category11 .dot{fill:#8c564b;stroke:transparent}.epoch.category20 .arc.category11 path{fill:#8c564b}.epoch.category20 .bar.category11{fill:#8c564b}.epoch.category20 div.ref.category12{background-color:#c49c94}.epoch.category20 .category12 .line{stroke:#c49c94}.epoch.category20 .category12 .area,.epoch.category20 .category12 .dot{fill:#c49c94;stroke:transparent}.epoch.category20 .arc.category12 path{fill:#c49c94}.epoch.category20 .bar.category12{fill:#c49c94}.epoch.category20 div.ref.category13{background-color:#e377c2}.epoch.category20 .category13 .line{stroke:#e377c2}.epoch.category20 .category13 .area,.epoch.category20 .category13 .dot{fill:#e377c2;stroke:transparent}.epoch.category20 .arc.category13 path{fill:#e377c2}.epoch.category20 .bar.category13{fill:#e377c2}.epoch.category20 div.ref.category14{background-color:#f7b6d2}.epoch.category20 .category14 .line{stroke:#f7b6d2}.epoch.category20 .category14 .area,.epoch.category20 .category14 .dot{fill:#f7b6d2;stroke:transparent}.epoch.category20 .arc.category14 path{fill:#f7b6d2}.epoch.category20 .bar.category14{fill:#f7b6d2}.epoch.category20 div.ref.category15{background-color:#7f7f7f}.epoch.category20 .category15 .line{stroke:#7f7f7f}.epoch.category20 .category15 .area,.epoch.category20 .category15 .dot{fill:#7f7f7f;stroke:transparent}.epoch.category20 .arc.category15 path{fill:#7f7f7f}.epoch.category20 .bar.category15{fill:#7f7f7f}.epoch.category20 div.ref.category16{background-color:#c7c7c7}.epoch.category20 .category16 .line{stroke:#c7c7c7}.epoch.category20 .category16 .area,.epoch.category20 .category16 .dot{fill:#c7c7c7;stroke:transparent}.epoch.category20 .arc.category16 path{fill:#c7c7c7}.epoch.category20 .bar.category16{fill:#c7c7c7}.epoch.category20 div.ref.category17{background-color:#bcbd22}.epoch.category20 .category17 .line{stroke:#bcbd22}.epoch.category20 .category17 .area,.epoch.category20 .category17 .dot{fill:#bcbd22;stroke:transparent}.epoch.category20 .arc.category17 path{fill:#bcbd22}.epoch.category20 .bar.category17{fill:#bcbd22}.epoch.category20 div.ref.category18{background-color:#dbdb8d}.epoch.category20 .category18 .line{stroke:#dbdb8d}.epoch.category20 .category18 .area,.epoch.category20 .category18 .dot{fill:#dbdb8d;stroke:transparent}.epoch.category20 .arc.category18 path{fill:#dbdb8d}.epoch.category20 .bar.category18{fill:#dbdb8d}.epoch.category20 div.ref.category19{background-color:#17becf}.epoch.category20 .category19 .line{stroke:#17becf}.epoch.category20 .category19 .area,.epoch.category20 .category19 .dot{fill:#17becf;stroke:transparent}.epoch.category20 .arc.category19 path{fill:#17becf}.epoch.category20 .bar.category19{fill:#17becf}.epoch.category20 div.ref.category20{background-color:#9edae5}.epoch.category20 .category20 .line{stroke:#9edae5}.epoch.category20 .category20 .area,.epoch.category20 .category20 .dot{fill:#9edae5;stroke:transparent}.epoch.category20 .arc.category20 path{fill:#9edae5}.epoch.category20 .bar.category20{fill:#9edae5}.epoch.category20b div.ref.category1{background-color:#393b79}.epoch.category20b .category1 .line{stroke:#393b79}.epoch.category20b .category1 .area,.epoch.category20b .category1 .dot{fill:#393b79;stroke:transparent}.epoch.category20b .arc.category1 path{fill:#393b79}.epoch.category20b .bar.category1{fill:#393b79}.epoch.category20b div.ref.category2{background-color:#5254a3}.epoch.category20b .category2 .line{stroke:#5254a3}.epoch.category20b .category2 .area,.epoch.category20b .category2 .dot{fill:#5254a3;stroke:transparent}.epoch.category20b .arc.category2 path{fill:#5254a3}.epoch.category20b .bar.category2{fill:#5254a3}.epoch.category20b div.ref.category3{background-color:#6b6ecf}.epoch.category20b .category3 .line{stroke:#6b6ecf}.epoch.category20b .category3 .area,.epoch.category20b .category3 .dot{fill:#6b6ecf;stroke:transparent}.epoch.category20b .arc.category3 path{fill:#6b6ecf}.epoch.category20b .bar.category3{fill:#6b6ecf}.epoch.category20b div.ref.category4{background-color:#9c9ede}.epoch.category20b .category4 .line{stroke:#9c9ede}.epoch.category20b .category4 .area,.epoch.category20b .category4 .dot{fill:#9c9ede;stroke:transparent}.epoch.category20b .arc.category4 path{fill:#9c9ede}.epoch.category20b .bar.category4{fill:#9c9ede}.epoch.category20b div.ref.category5{background-color:#637939}.epoch.category20b .category5 .line{stroke:#637939}.epoch.category20b .category5 .area,.epoch.category20b .category5 .dot{fill:#637939;stroke:transparent}.epoch.category20b .arc.category5 path{fill:#637939}.epoch.category20b .bar.category5{fill:#637939}.epoch.category20b div.ref.category6{background-color:#8ca252}.epoch.category20b .category6 .line{stroke:#8ca252}.epoch.category20b .category6 .area,.epoch.category20b .category6 .dot{fill:#8ca252;stroke:transparent}.epoch.category20b .arc.category6 path{fill:#8ca252}.epoch.category20b .bar.category6{fill:#8ca252}.epoch.category20b div.ref.category7{background-color:#b5cf6b}.epoch.category20b .category7 .line{stroke:#b5cf6b}.epoch.category20b .category7 .area,.epoch.category20b .category7 .dot{fill:#b5cf6b;stroke:transparent}.epoch.category20b .arc.category7 path{fill:#b5cf6b}.epoch.category20b .bar.category7{fill:#b5cf6b}.epoch.category20b div.ref.category8{background-color:#cedb9c}.epoch.category20b .category8 .line{stroke:#cedb9c}.epoch.category20b .category8 .area,.epoch.category20b .category8 .dot{fill:#cedb9c;stroke:transparent}.epoch.category20b .arc.category8 path{fill:#cedb9c}.epoch.category20b .bar.category8{fill:#cedb9c}.epoch.category20b div.ref.category9{background-color:#8c6d31}.epoch.category20b .category9 .line{stroke:#8c6d31}.epoch.category20b .category9 .area,.epoch.category20b .category9 .dot{fill:#8c6d31;stroke:transparent}.epoch.category20b .arc.category9 path{fill:#8c6d31}.epoch.category20b .bar.category9{fill:#8c6d31}.epoch.category20b div.ref.category10{background-color:#bd9e39}.epoch.category20b .category10 .line{stroke:#bd9e39}.epoch.category20b .category10 .area,.epoch.category20b .category10 .dot{fill:#bd9e39;stroke:transparent}.epoch.category20b .arc.category10 path{fill:#bd9e39}.epoch.category20b .bar.category10{fill:#bd9e39}.epoch.category20b div.ref.category11{background-color:#e7ba52}.epoch.category20b .category11 .line{stroke:#e7ba52}.epoch.category20b .category11 .area,.epoch.category20b .category11 .dot{fill:#e7ba52;stroke:transparent}.epoch.category20b .arc.category11 path{fill:#e7ba52}.epoch.category20b .bar.category11{fill:#e7ba52}.epoch.category20b div.ref.category12{background-color:#e7cb94}.epoch.category20b .category12 .line{stroke:#e7cb94}.epoch.category20b .category12 .area,.epoch.category20b .category12 .dot{fill:#e7cb94;stroke:transparent}.epoch.category20b .arc.category12 path{fill:#e7cb94}.epoch.category20b .bar.category12{fill:#e7cb94}.epoch.category20b div.ref.category13{background-color:#843c39}.epoch.category20b .category13 .line{stroke:#843c39}.epoch.category20b .category13 .area,.epoch.category20b .category13 .dot{fill:#843c39;stroke:transparent}.epoch.category20b .arc.category13 path{fill:#843c39}.epoch.category20b .bar.category13{fill:#843c39}.epoch.category20b div.ref.category14{background-color:#ad494a}.epoch.category20b .category14 .line{stroke:#ad494a}.epoch.category20b .category14 .area,.epoch.category20b .category14 .dot{fill:#ad494a;stroke:transparent}.epoch.category20b .arc.category14 path{fill:#ad494a}.epoch.category20b .bar.category14{fill:#ad494a}.epoch.category20b div.ref.category15{background-color:#d6616b}.epoch.category20b .category15 .line{stroke:#d6616b}.epoch.category20b .category15 .area,.epoch.category20b .category15 .dot{fill:#d6616b;stroke:transparent}.epoch.category20b .arc.category15 path{fill:#d6616b}.epoch.category20b .bar.category15{fill:#d6616b}.epoch.category20b div.ref.category16{background-color:#e7969c}.epoch.category20b .category16 .line{stroke:#e7969c}.epoch.category20b .category16 .area,.epoch.category20b .category16 .dot{fill:#e7969c;stroke:transparent}.epoch.category20b .arc.category16 path{fill:#e7969c}.epoch.category20b .bar.category16{fill:#e7969c}.epoch.category20b div.ref.category17{background-color:#7b4173}.epoch.category20b .category17 .line{stroke:#7b4173}.epoch.category20b .category17 .area,.epoch.category20b .category17 .dot{fill:#7b4173;stroke:transparent}.epoch.category20b .arc.category17 path{fill:#7b4173}.epoch.category20b .bar.category17{fill:#7b4173}.epoch.category20b div.ref.category18{background-color:#a55194}.epoch.category20b .category18 .line{stroke:#a55194}.epoch.category20b .category18 .area,.epoch.category20b .category18 .dot{fill:#a55194;stroke:transparent}.epoch.category20b .arc.category18 path{fill:#a55194}.epoch.category20b .bar.category18{fill:#a55194}.epoch.category20b div.ref.category19{background-color:#ce6dbd}.epoch.category20b .category19 .line{stroke:#ce6dbd}.epoch.category20b .category19 .area,.epoch.category20b .category19 .dot{fill:#ce6dbd;stroke:transparent}.epoch.category20b .arc.category19 path{fill:#ce6dbd}.epoch.category20b .bar.category19{fill:#ce6dbd}.epoch.category20b div.ref.category20{background-color:#de9ed6}.epoch.category20b .category20 .line{stroke:#de9ed6}.epoch.category20b .category20 .area,.epoch.category20b .category20 .dot{fill:#de9ed6;stroke:transparent}.epoch.category20b .arc.category20 path{fill:#de9ed6}.epoch.category20b .bar.category20{fill:#de9ed6}.epoch.category20c div.ref.category1{background-color:#3182bd}.epoch.category20c .category1 .line{stroke:#3182bd}.epoch.category20c .category1 .area,.epoch.category20c .category1 .dot{fill:#3182bd;stroke:transparent}.epoch.category20c .arc.category1 path{fill:#3182bd}.epoch.category20c .bar.category1{fill:#3182bd}.epoch.category20c div.ref.category2{background-color:#6baed6}.epoch.category20c .category2 .line{stroke:#6baed6}.epoch.category20c .category2 .area,.epoch.category20c .category2 .dot{fill:#6baed6;stroke:transparent}.epoch.category20c .arc.category2 path{fill:#6baed6}.epoch.category20c .bar.category2{fill:#6baed6}.epoch.category20c div.ref.category3{background-color:#9ecae1}.epoch.category20c .category3 .line{stroke:#9ecae1}.epoch.category20c .category3 .area,.epoch.category20c .category3 .dot{fill:#9ecae1;stroke:transparent}.epoch.category20c .arc.category3 path{fill:#9ecae1}.epoch.category20c .bar.category3{fill:#9ecae1}.epoch.category20c div.ref.category4{background-color:#c6dbef}.epoch.category20c .category4 .line{stroke:#c6dbef}.epoch.category20c .category4 .area,.epoch.category20c .category4 .dot{fill:#c6dbef;stroke:transparent}.epoch.category20c .arc.category4 path{fill:#c6dbef}.epoch.category20c .bar.category4{fill:#c6dbef}.epoch.category20c div.ref.category5{background-color:#e6550d}.epoch.category20c .category5 .line{stroke:#e6550d}.epoch.category20c .category5 .area,.epoch.category20c .category5 .dot{fill:#e6550d;stroke:transparent}.epoch.category20c .arc.category5 path{fill:#e6550d}.epoch.category20c .bar.category5{fill:#e6550d}.epoch.category20c div.ref.category6{background-color:#fd8d3c}.epoch.category20c .category6 .line{stroke:#fd8d3c}.epoch.category20c .category6 .area,.epoch.category20c .category6 .dot{fill:#fd8d3c;stroke:transparent}.epoch.category20c .arc.category6 path{fill:#fd8d3c}.epoch.category20c .bar.category6{fill:#fd8d3c}.epoch.category20c div.ref.category7{background-color:#fdae6b}.epoch.category20c .category7 .line{stroke:#fdae6b}.epoch.category20c .category7 .area,.epoch.category20c .category7 .dot{fill:#fdae6b;stroke:transparent}.epoch.category20c .arc.category7 path{fill:#fdae6b}.epoch.category20c .bar.category7{fill:#fdae6b}.epoch.category20c div.ref.category8{background-color:#fdd0a2}.epoch.category20c .category8 .line{stroke:#fdd0a2}.epoch.category20c .category8 .area,.epoch.category20c .category8 .dot{fill:#fdd0a2;stroke:transparent}.epoch.category20c .arc.category8 path{fill:#fdd0a2}.epoch.category20c .bar.category8{fill:#fdd0a2}.epoch.category20c div.ref.category9{background-color:#31a354}.epoch.category20c .category9 .line{stroke:#31a354}.epoch.category20c .category9 .area,.epoch.category20c .category9 .dot{fill:#31a354;stroke:transparent}.epoch.category20c .arc.category9 path{fill:#31a354}.epoch.category20c .bar.category9{fill:#31a354}.epoch.category20c div.ref.category10{background-color:#74c476}.epoch.category20c .category10 .line{stroke:#74c476}.epoch.category20c .category10 .area,.epoch.category20c .category10 .dot{fill:#74c476;stroke:transparent}.epoch.category20c .arc.category10 path{fill:#74c476}.epoch.category20c .bar.category10{fill:#74c476}.epoch.category20c div.ref.category11{background-color:#a1d99b}.epoch.category20c .category11 .line{stroke:#a1d99b}.epoch.category20c .category11 .area,.epoch.category20c .category11 .dot{fill:#a1d99b;stroke:transparent}.epoch.category20c .arc.category11 path{fill:#a1d99b}.epoch.category20c .bar.category11{fill:#a1d99b}.epoch.category20c div.ref.category12{background-color:#c7e9c0}.epoch.category20c .category12 .line{stroke:#c7e9c0}.epoch.category20c .category12 .area,.epoch.category20c .category12 .dot{fill:#c7e9c0;stroke:transparent}.epoch.category20c .arc.category12 path{fill:#c7e9c0}.epoch.category20c .bar.category12{fill:#c7e9c0}.epoch.category20c div.ref.category13{background-color:#756bb1}.epoch.category20c .category13 .line{stroke:#756bb1}.epoch.category20c .category13 .area,.epoch.category20c .category13 .dot{fill:#756bb1;stroke:transparent}.epoch.category20c .arc.category13 path{fill:#756bb1}.epoch.category20c .bar.category13{fill:#756bb1}.epoch.category20c div.ref.category14{background-color:#9e9ac8}.epoch.category20c .category14 .line{stroke:#9e9ac8}.epoch.category20c .category14 .area,.epoch.category20c .category14 .dot{fill:#9e9ac8;stroke:transparent}.epoch.category20c .arc.category14 path{fill:#9e9ac8}.epoch.category20c .bar.category14{fill:#9e9ac8}.epoch.category20c div.ref.category15{background-color:#bcbddc}.epoch.category20c .category15 .line{stroke:#bcbddc}.epoch.category20c .category15 .area,.epoch.category20c .category15 .dot{fill:#bcbddc;stroke:transparent}.epoch.category20c .arc.category15 path{fill:#bcbddc}.epoch.category20c .bar.category15{fill:#bcbddc}.epoch.category20c div.ref.category16{background-color:#dadaeb}.epoch.category20c .category16 .line{stroke:#dadaeb}.epoch.category20c .category16 .area,.epoch.category20c .category16 .dot{fill:#dadaeb;stroke:transparent}.epoch.category20c .arc.category16 path{fill:#dadaeb}.epoch.category20c .bar.category16{fill:#dadaeb}.epoch.category20c div.ref.category17{background-color:#636363}.epoch.category20c .category17 .line{stroke:#636363}.epoch.category20c .category17 .area,.epoch.category20c .category17 .dot{fill:#636363;stroke:transparent}.epoch.category20c .arc.category17 path{fill:#636363}.epoch.category20c .bar.category17{fill:#636363}.epoch.category20c div.ref.category18{background-color:#969696}.epoch.category20c .category18 .line{stroke:#969696}.epoch.category20c .category18 .area,.epoch.category20c .category18 .dot{fill:#969696;stroke:transparent}.epoch.category20c .arc.category18 path{fill:#969696}.epoch.category20c .bar.category18{fill:#969696}.epoch.category20c div.ref.category19{background-color:#bdbdbd}.epoch.category20c .category19 .line{stroke:#bdbdbd}.epoch.category20c .category19 .area,.epoch.category20c .category19 .dot{fill:#bdbdbd;stroke:transparent}.epoch.category20c .arc.category19 path{fill:#bdbdbd}.epoch.category20c .bar.category19{fill:#bdbdbd}.epoch.category20c div.ref.category20{background-color:#d9d9d9}.epoch.category20c .category20 .line{stroke:#d9d9d9}.epoch.category20c .category20 .area,.epoch.category20c .category20 .dot{fill:#d9d9d9;stroke:transparent}.epoch.category20c .arc.category20 path{fill:#d9d9d9}.epoch.category20c .bar.category20{fill:#d9d9d9}.epoch .category1 .bucket,.epoch.heatmap5 .category1 .bucket{fill:#1f77b4}.epoch .category2 .bucket,.epoch.heatmap5 .category2 .bucket{fill:#2ca02c}.epoch .category3 .bucket,.epoch.heatmap5 .category3 .bucket{fill:#d62728}.epoch .category4 .bucket,.epoch.heatmap5 .category4 .bucket{fill:#8c564b}.epoch .category5 .bucket,.epoch.heatmap5 .category5 .bucket{fill:#7f7f7f}.epoch-theme-dark .epoch .axis path,.epoch-theme-dark .epoch .axis line{stroke:#d0d0d0}.epoch-theme-dark .epoch .axis .tick text{fill:#d0d0d0}.epoch-theme-dark .arc.pie{stroke:#333}.epoch-theme-dark .arc.pie text{fill:#333}.epoch-theme-dark .epoch .gauge-labels .value{fill:#BBB}.epoch-theme-dark .epoch .gauge .arc.outer{stroke:#999}.epoch-theme-dark .epoch .gauge .arc.inner{stroke:#AAA}.epoch-theme-dark .epoch .gauge .tick{stroke:#AAA}.epoch-theme-dark .epoch .gauge .needle{fill:#F3DE88}.epoch-theme-dark .epoch .gauge .needle-base{fill:#999}.epoch-theme-dark .epoch div.ref.category1,.epoch-theme-dark .epoch.category10 div.ref.category1{background-color:#909CFF}.epoch-theme-dark .epoch .category1 .line,.epoch-theme-dark .epoch.category10 .category1 .line{stroke:#909CFF}.epoch-theme-dark .epoch .category1 .area,.epoch-theme-dark .epoch .category1 .dot,.epoch-theme-dark .epoch.category10 .category1 .area,.epoch-theme-dark .epoch.category10 .category1 .dot{fill:#909CFF;stroke:transparent}.epoch-theme-dark .epoch .arc.category1 path,.epoch-theme-dark .epoch.category10 .arc.category1 path{fill:#909CFF}.epoch-theme-dark .epoch .bar.category1,.epoch-theme-dark .epoch.category10 .bar.category1{fill:#909CFF}.epoch-theme-dark .epoch div.ref.category2,.epoch-theme-dark .epoch.category10 div.ref.category2{background-color:#FFAC89}.epoch-theme-dark .epoch .category2 .line,.epoch-theme-dark .epoch.category10 .category2 .line{stroke:#FFAC89}.epoch-theme-dark .epoch .category2 .area,.epoch-theme-dark .epoch .category2 .dot,.epoch-theme-dark .epoch.category10 .category2 .area,.epoch-theme-dark .epoch.category10 .category2 .dot{fill:#FFAC89;stroke:transparent}.epoch-theme-dark .epoch .arc.category2 path,.epoch-theme-dark .epoch.category10 .arc.category2 path{fill:#FFAC89}.epoch-theme-dark .epoch .bar.category2,.epoch-theme-dark .epoch.category10 .bar.category2{fill:#FFAC89}.epoch-theme-dark .epoch div.ref.category3,.epoch-theme-dark .epoch.category10 div.ref.category3{background-color:#E889E8}.epoch-theme-dark .epoch .category3 .line,.epoch-theme-dark .epoch.category10 .category3 .line{stroke:#E889E8}.epoch-theme-dark .epoch .category3 .area,.epoch-theme-dark .epoch .category3 .dot,.epoch-theme-dark .epoch.category10 .category3 .area,.epoch-theme-dark .epoch.category10 .category3 .dot{fill:#E889E8;stroke:transparent}.epoch-theme-dark .epoch .arc.category3 path,.epoch-theme-dark .epoch.category10 .arc.category3 path{fill:#E889E8}.epoch-theme-dark .epoch .bar.category3,.epoch-theme-dark .epoch.category10 .bar.category3{fill:#E889E8}.epoch-theme-dark .epoch div.ref.category4,.epoch-theme-dark .epoch.category10 div.ref.category4{background-color:#78E8D3}.epoch-theme-dark .epoch .category4 .line,.epoch-theme-dark .epoch.category10 .category4 .line{stroke:#78E8D3}.epoch-theme-dark .epoch .category4 .area,.epoch-theme-dark .epoch .category4 .dot,.epoch-theme-dark .epoch.category10 .category4 .area,.epoch-theme-dark .epoch.category10 .category4 .dot{fill:#78E8D3;stroke:transparent}.epoch-theme-dark .epoch .arc.category4 path,.epoch-theme-dark .epoch.category10 .arc.category4 path{fill:#78E8D3}.epoch-theme-dark .epoch .bar.category4,.epoch-theme-dark .epoch.category10 .bar.category4{fill:#78E8D3}.epoch-theme-dark .epoch div.ref.category5,.epoch-theme-dark .epoch.category10 div.ref.category5{background-color:#C2FF97}.epoch-theme-dark .epoch .category5 .line,.epoch-theme-dark .epoch.category10 .category5 .line{stroke:#C2FF97}.epoch-theme-dark .epoch .category5 .area,.epoch-theme-dark .epoch .category5 .dot,.epoch-theme-dark .epoch.category10 .category5 .area,.epoch-theme-dark .epoch.category10 .category5 .dot{fill:#C2FF97;stroke:transparent}.epoch-theme-dark .epoch .arc.category5 path,.epoch-theme-dark .epoch.category10 .arc.category5 path{fill:#C2FF97}.epoch-theme-dark .epoch .bar.category5,.epoch-theme-dark .epoch.category10 .bar.category5{fill:#C2FF97}.epoch-theme-dark .epoch div.ref.category6,.epoch-theme-dark .epoch.category10 div.ref.category6{background-color:#B7BCD1}.epoch-theme-dark .epoch .category6 .line,.epoch-theme-dark .epoch.category10 .category6 .line{stroke:#B7BCD1}.epoch-theme-dark .epoch .category6 .area,.epoch-theme-dark .epoch .category6 .dot,.epoch-theme-dark .epoch.category10 .category6 .area,.epoch-theme-dark .epoch.category10 .category6 .dot{fill:#B7BCD1;stroke:transparent}.epoch-theme-dark .epoch .arc.category6 path,.epoch-theme-dark .epoch.category10 .arc.category6 path{fill:#B7BCD1}.epoch-theme-dark .epoch .bar.category6,.epoch-theme-dark .epoch.category10 .bar.category6{fill:#B7BCD1}.epoch-theme-dark .epoch div.ref.category7,.epoch-theme-dark .epoch.category10 div.ref.category7{background-color:#FF857F}.epoch-theme-dark .epoch .category7 .line,.epoch-theme-dark .epoch.category10 .category7 .line{stroke:#FF857F}.epoch-theme-dark .epoch .category7 .area,.epoch-theme-dark .epoch .category7 .dot,.epoch-theme-dark .epoch.category10 .category7 .area,.epoch-theme-dark .epoch.category10 .category7 .dot{fill:#FF857F;stroke:transparent}.epoch-theme-dark .epoch .arc.category7 path,.epoch-theme-dark .epoch.category10 .arc.category7 path{fill:#FF857F}.epoch-theme-dark .epoch .bar.category7,.epoch-theme-dark .epoch.category10 .bar.category7{fill:#FF857F}.epoch-theme-dark .epoch div.ref.category8,.epoch-theme-dark .epoch.category10 div.ref.category8{background-color:#F3DE88}.epoch-theme-dark .epoch .category8 .line,.epoch-theme-dark .epoch.category10 .category8 .line{stroke:#F3DE88}.epoch-theme-dark .epoch .category8 .area,.epoch-theme-dark .epoch .category8 .dot,.epoch-theme-dark .epoch.category10 .category8 .area,.epoch-theme-dark .epoch.category10 .category8 .dot{fill:#F3DE88;stroke:transparent}.epoch-theme-dark .epoch .arc.category8 path,.epoch-theme-dark .epoch.category10 .arc.category8 path{fill:#F3DE88}.epoch-theme-dark .epoch .bar.category8,.epoch-theme-dark .epoch.category10 .bar.category8{fill:#F3DE88}.epoch-theme-dark .epoch div.ref.category9,.epoch-theme-dark .epoch.category10 div.ref.category9{background-color:#C9935E}.epoch-theme-dark .epoch .category9 .line,.epoch-theme-dark .epoch.category10 .category9 .line{stroke:#C9935E}.epoch-theme-dark .epoch .category9 .area,.epoch-theme-dark .epoch .category9 .dot,.epoch-theme-dark .epoch.category10 .category9 .area,.epoch-theme-dark .epoch.category10 .category9 .dot{fill:#C9935E;stroke:transparent}.epoch-theme-dark .epoch .arc.category9 path,.epoch-theme-dark .epoch.category10 .arc.category9 path{fill:#C9935E}.epoch-theme-dark .epoch .bar.category9,.epoch-theme-dark .epoch.category10 .bar.category9{fill:#C9935E}.epoch-theme-dark .epoch div.ref.category10,.epoch-theme-dark .epoch.category10 div.ref.category10{background-color:#A488FF}.epoch-theme-dark .epoch .category10 .line,.epoch-theme-dark .epoch.category10 .category10 .line{stroke:#A488FF}.epoch-theme-dark .epoch .category10 .area,.epoch-theme-dark .epoch .category10 .dot,.epoch-theme-dark .epoch.category10 .category10 .area,.epoch-theme-dark .epoch.category10 .category10 .dot{fill:#A488FF;stroke:transparent}.epoch-theme-dark .epoch .arc.category10 path,.epoch-theme-dark .epoch.category10 .arc.category10 path{fill:#A488FF}.epoch-theme-dark .epoch .bar.category10,.epoch-theme-dark .epoch.category10 .bar.category10{fill:#A488FF}.epoch-theme-dark .epoch.category20 div.ref.category1{background-color:#909CFF}.epoch-theme-dark .epoch.category20 .category1 .line{stroke:#909CFF}.epoch-theme-dark .epoch.category20 .category1 .area,.epoch-theme-dark .epoch.category20 .category1 .dot{fill:#909CFF;stroke:transparent}.epoch-theme-dark .epoch.category20 .arc.category1 path{fill:#909CFF}.epoch-theme-dark .epoch.category20 .bar.category1{fill:#909CFF}.epoch-theme-dark .epoch.category20 div.ref.category2{background-color:#626AAD}.epoch-theme-dark .epoch.category20 .category2 .line{stroke:#626AAD}.epoch-theme-dark .epoch.category20 .category2 .area,.epoch-theme-dark .epoch.category20 .category2 .dot{fill:#626AAD;stroke:transparent}.epoch-theme-dark .epoch.category20 .arc.category2 path{fill:#626AAD}.epoch-theme-dark .epoch.category20 .bar.category2{fill:#626AAD}.epoch-theme-dark .epoch.category20 div.ref.category3{background-color:#FFAC89}.epoch-theme-dark .epoch.category20 .category3 .line{stroke:#FFAC89}.epoch-theme-dark .epoch.category20 .category3 .area,.epoch-theme-dark .epoch.category20 .category3 .dot{fill:#FFAC89;stroke:transparent}.epoch-theme-dark .epoch.category20 .arc.category3 path{fill:#FFAC89}.epoch-theme-dark .epoch.category20 .bar.category3{fill:#FFAC89}.epoch-theme-dark .epoch.category20 div.ref.category4{background-color:#BD7F66}.epoch-theme-dark .epoch.category20 .category4 .line{stroke:#BD7F66}.epoch-theme-dark .epoch.category20 .category4 .area,.epoch-theme-dark .epoch.category20 .category4 .dot{fill:#BD7F66;stroke:transparent}.epoch-theme-dark .epoch.category20 .arc.category4 path{fill:#BD7F66}.epoch-theme-dark .epoch.category20 .bar.category4{fill:#BD7F66}.epoch-theme-dark .epoch.category20 div.ref.category5{background-color:#E889E8}.epoch-theme-dark .epoch.category20 .category5 .line{stroke:#E889E8}.epoch-theme-dark .epoch.category20 .category5 .area,.epoch-theme-dark .epoch.category20 .category5 .dot{fill:#E889E8;stroke:transparent}.epoch-theme-dark .epoch.category20 .arc.category5 path{fill:#E889E8}.epoch-theme-dark .epoch.category20 .bar.category5{fill:#E889E8}.epoch-theme-dark .epoch.category20 div.ref.category6{background-color:#995A99}.epoch-theme-dark .epoch.category20 .category6 .line{stroke:#995A99}.epoch-theme-dark .epoch.category20 .category6 .area,.epoch-theme-dark .epoch.category20 .category6 .dot{fill:#995A99;stroke:transparent}.epoch-theme-dark .epoch.category20 .arc.category6 path{fill:#995A99}.epoch-theme-dark .epoch.category20 .bar.category6{fill:#995A99}.epoch-theme-dark .epoch.category20 div.ref.category7{background-color:#78E8D3}.epoch-theme-dark .epoch.category20 .category7 .line{stroke:#78E8D3}.epoch-theme-dark .epoch.category20 .category7 .area,.epoch-theme-dark .epoch.category20 .category7 .dot{fill:#78E8D3;stroke:transparent}.epoch-theme-dark .epoch.category20 .arc.category7 path{fill:#78E8D3}.epoch-theme-dark .epoch.category20 .bar.category7{fill:#78E8D3}.epoch-theme-dark .epoch.category20 div.ref.category8{background-color:#4F998C}.epoch-theme-dark .epoch.category20 .category8 .line{stroke:#4F998C}.epoch-theme-dark .epoch.category20 .category8 .area,.epoch-theme-dark .epoch.category20 .category8 .dot{fill:#4F998C;stroke:transparent}.epoch-theme-dark .epoch.category20 .arc.category8 path{fill:#4F998C}.epoch-theme-dark .epoch.category20 .bar.category8{fill:#4F998C}.epoch-theme-dark .epoch.category20 div.ref.category9{background-color:#C2FF97}.epoch-theme-dark .epoch.category20 .category9 .line{stroke:#C2FF97}.epoch-theme-dark .epoch.category20 .category9 .area,.epoch-theme-dark .epoch.category20 .category9 .dot{fill:#C2FF97;stroke:transparent}.epoch-theme-dark .epoch.category20 .arc.category9 path{fill:#C2FF97}.epoch-theme-dark .epoch.category20 .bar.category9{fill:#C2FF97}.epoch-theme-dark .epoch.category20 div.ref.category10{background-color:#789E5E}.epoch-theme-dark .epoch.category20 .category10 .line{stroke:#789E5E}.epoch-theme-dark .epoch.category20 .category10 .area,.epoch-theme-dark .epoch.category20 .category10 .dot{fill:#789E5E;stroke:transparent}.epoch-theme-dark .epoch.category20 .arc.category10 path{fill:#789E5E}.epoch-theme-dark .epoch.category20 .bar.category10{fill:#789E5E}.epoch-theme-dark .epoch.category20 div.ref.category11{background-color:#B7BCD1}.epoch-theme-dark .epoch.category20 .category11 .line{stroke:#B7BCD1}.epoch-theme-dark .epoch.category20 .category11 .area,.epoch-theme-dark .epoch.category20 .category11 .dot{fill:#B7BCD1;stroke:transparent}.epoch-theme-dark .epoch.category20 .arc.category11 path{fill:#B7BCD1}.epoch-theme-dark .epoch.category20 .bar.category11{fill:#B7BCD1}.epoch-theme-dark .epoch.category20 div.ref.category12{background-color:#7F8391}.epoch-theme-dark .epoch.category20 .category12 .line{stroke:#7F8391}.epoch-theme-dark .epoch.category20 .category12 .area,.epoch-theme-dark .epoch.category20 .category12 .dot{fill:#7F8391;stroke:transparent}.epoch-theme-dark .epoch.category20 .arc.category12 path{fill:#7F8391}.epoch-theme-dark .epoch.category20 .bar.category12{fill:#7F8391}.epoch-theme-dark .epoch.category20 div.ref.category13{background-color:#CCB889}.epoch-theme-dark .epoch.category20 .category13 .line{stroke:#CCB889}.epoch-theme-dark .epoch.category20 .category13 .area,.epoch-theme-dark .epoch.category20 .category13 .dot{fill:#CCB889;stroke:transparent}.epoch-theme-dark .epoch.category20 .arc.category13 path{fill:#CCB889}.epoch-theme-dark .epoch.category20 .bar.category13{fill:#CCB889}.epoch-theme-dark .epoch.category20 div.ref.category14{background-color:#A1906B}.epoch-theme-dark .epoch.category20 .category14 .line{stroke:#A1906B}.epoch-theme-dark .epoch.category20 .category14 .area,.epoch-theme-dark .epoch.category20 .category14 .dot{fill:#A1906B;stroke:transparent}.epoch-theme-dark .epoch.category20 .arc.category14 path{fill:#A1906B}.epoch-theme-dark .epoch.category20 .bar.category14{fill:#A1906B}.epoch-theme-dark .epoch.category20 div.ref.category15{background-color:#F3DE88}.epoch-theme-dark .epoch.category20 .category15 .line{stroke:#F3DE88}.epoch-theme-dark .epoch.category20 .category15 .area,.epoch-theme-dark .epoch.category20 .category15 .dot{fill:#F3DE88;stroke:transparent}.epoch-theme-dark .epoch.category20 .arc.category15 path{fill:#F3DE88}.epoch-theme-dark .epoch.category20 .bar.category15{fill:#F3DE88}.epoch-theme-dark .epoch.category20 div.ref.category16{background-color:#A89A5E}.epoch-theme-dark .epoch.category20 .category16 .line{stroke:#A89A5E}.epoch-theme-dark .epoch.category20 .category16 .area,.epoch-theme-dark .epoch.category20 .category16 .dot{fill:#A89A5E;stroke:transparent}.epoch-theme-dark .epoch.category20 .arc.category16 path{fill:#A89A5E}.epoch-theme-dark .epoch.category20 .bar.category16{fill:#A89A5E}.epoch-theme-dark .epoch.category20 div.ref.category17{background-color:#FF857F}.epoch-theme-dark .epoch.category20 .category17 .line{stroke:#FF857F}.epoch-theme-dark .epoch.category20 .category17 .area,.epoch-theme-dark .epoch.category20 .category17 .dot{fill:#FF857F;stroke:transparent}.epoch-theme-dark .epoch.category20 .arc.category17 path{fill:#FF857F}.epoch-theme-dark .epoch.category20 .bar.category17{fill:#FF857F}.epoch-theme-dark .epoch.category20 div.ref.category18{background-color:#BA615D}.epoch-theme-dark .epoch.category20 .category18 .line{stroke:#BA615D}.epoch-theme-dark .epoch.category20 .category18 .area,.epoch-theme-dark .epoch.category20 .category18 .dot{fill:#BA615D;stroke:transparent}.epoch-theme-dark .epoch.category20 .arc.category18 path{fill:#BA615D}.epoch-theme-dark .epoch.category20 .bar.category18{fill:#BA615D}.epoch-theme-dark .epoch.category20 div.ref.category19{background-color:#A488FF}.epoch-theme-dark .epoch.category20 .category19 .line{stroke:#A488FF}.epoch-theme-dark .epoch.category20 .category19 .area,.epoch-theme-dark .epoch.category20 .category19 .dot{fill:#A488FF;stroke:transparent}.epoch-theme-dark .epoch.category20 .arc.category19 path{fill:#A488FF}.epoch-theme-dark .epoch.category20 .bar.category19{fill:#A488FF}.epoch-theme-dark .epoch.category20 div.ref.category20{background-color:#7662B8}.epoch-theme-dark .epoch.category20 .category20 .line{stroke:#7662B8}.epoch-theme-dark .epoch.category20 .category20 .area,.epoch-theme-dark .epoch.category20 .category20 .dot{fill:#7662B8;stroke:transparent}.epoch-theme-dark .epoch.category20 .arc.category20 path{fill:#7662B8}.epoch-theme-dark .epoch.category20 .bar.category20{fill:#7662B8}.epoch-theme-dark .epoch.category20b div.ref.category1{background-color:#909CFF}.epoch-theme-dark .epoch.category20b .category1 .line{stroke:#909CFF}.epoch-theme-dark .epoch.category20b .category1 .area,.epoch-theme-dark .epoch.category20b .category1 .dot{fill:#909CFF;stroke:transparent}.epoch-theme-dark .epoch.category20b .arc.category1 path{fill:#909CFF}.epoch-theme-dark .epoch.category20b .bar.category1{fill:#909CFF}.epoch-theme-dark .epoch.category20b div.ref.category2{background-color:#7680D1}.epoch-theme-dark .epoch.category20b .category2 .line{stroke:#7680D1}.epoch-theme-dark .epoch.category20b .category2 .area,.epoch-theme-dark .epoch.category20b .category2 .dot{fill:#7680D1;stroke:transparent}.epoch-theme-dark .epoch.category20b .arc.category2 path{fill:#7680D1}.epoch-theme-dark .epoch.category20b .bar.category2{fill:#7680D1}.epoch-theme-dark .epoch.category20b div.ref.category3{background-color:#656DB2}.epoch-theme-dark .epoch.category20b .category3 .line{stroke:#656DB2}.epoch-theme-dark .epoch.category20b .category3 .area,.epoch-theme-dark .epoch.category20b .category3 .dot{fill:#656DB2;stroke:transparent}.epoch-theme-dark .epoch.category20b .arc.category3 path{fill:#656DB2}.epoch-theme-dark .epoch.category20b .bar.category3{fill:#656DB2}.epoch-theme-dark .epoch.category20b div.ref.category4{background-color:#525992}.epoch-theme-dark .epoch.category20b .category4 .line{stroke:#525992}.epoch-theme-dark .epoch.category20b .category4 .area,.epoch-theme-dark .epoch.category20b .category4 .dot{fill:#525992;stroke:transparent}.epoch-theme-dark .epoch.category20b .arc.category4 path{fill:#525992}.epoch-theme-dark .epoch.category20b .bar.category4{fill:#525992}.epoch-theme-dark .epoch.category20b div.ref.category5{background-color:#FFAC89}.epoch-theme-dark .epoch.category20b .category5 .line{stroke:#FFAC89}.epoch-theme-dark .epoch.category20b .category5 .area,.epoch-theme-dark .epoch.category20b .category5 .dot{fill:#FFAC89;stroke:transparent}.epoch-theme-dark .epoch.category20b .arc.category5 path{fill:#FFAC89}.epoch-theme-dark .epoch.category20b .bar.category5{fill:#FFAC89}.epoch-theme-dark .epoch.category20b div.ref.category6{background-color:#D18D71}.epoch-theme-dark .epoch.category20b .category6 .line{stroke:#D18D71}.epoch-theme-dark .epoch.category20b .category6 .area,.epoch-theme-dark .epoch.category20b .category6 .dot{fill:#D18D71;stroke:transparent}.epoch-theme-dark .epoch.category20b .arc.category6 path{fill:#D18D71}.epoch-theme-dark .epoch.category20b .bar.category6{fill:#D18D71}.epoch-theme-dark .epoch.category20b div.ref.category7{background-color:#AB735C}.epoch-theme-dark .epoch.category20b .category7 .line{stroke:#AB735C}.epoch-theme-dark .epoch.category20b .category7 .area,.epoch-theme-dark .epoch.category20b .category7 .dot{fill:#AB735C;stroke:transparent}.epoch-theme-dark .epoch.category20b .arc.category7 path{fill:#AB735C}.epoch-theme-dark .epoch.category20b .bar.category7{fill:#AB735C}.epoch-theme-dark .epoch.category20b div.ref.category8{background-color:#92624E}.epoch-theme-dark .epoch.category20b .category8 .line{stroke:#92624E}.epoch-theme-dark .epoch.category20b .category8 .area,.epoch-theme-dark .epoch.category20b .category8 .dot{fill:#92624E;stroke:transparent}.epoch-theme-dark .epoch.category20b .arc.category8 path{fill:#92624E}.epoch-theme-dark .epoch.category20b .bar.category8{fill:#92624E}.epoch-theme-dark .epoch.category20b div.ref.category9{background-color:#E889E8}.epoch-theme-dark .epoch.category20b .category9 .line{stroke:#E889E8}.epoch-theme-dark .epoch.category20b .category9 .area,.epoch-theme-dark .epoch.category20b .category9 .dot{fill:#E889E8;stroke:transparent}.epoch-theme-dark .epoch.category20b .arc.category9 path{fill:#E889E8}.epoch-theme-dark .epoch.category20b .bar.category9{fill:#E889E8}.epoch-theme-dark .epoch.category20b div.ref.category10{background-color:#BA6EBA}.epoch-theme-dark .epoch.category20b .category10 .line{stroke:#BA6EBA}.epoch-theme-dark .epoch.category20b .category10 .area,.epoch-theme-dark .epoch.category20b .category10 .dot{fill:#BA6EBA;stroke:transparent}.epoch-theme-dark .epoch.category20b .arc.category10 path{fill:#BA6EBA}.epoch-theme-dark .epoch.category20b .bar.category10{fill:#BA6EBA}.epoch-theme-dark .epoch.category20b div.ref.category11{background-color:#9B5C9B}.epoch-theme-dark .epoch.category20b .category11 .line{stroke:#9B5C9B}.epoch-theme-dark .epoch.category20b .category11 .area,.epoch-theme-dark .epoch.category20b .category11 .dot{fill:#9B5C9B;stroke:transparent}.epoch-theme-dark .epoch.category20b .arc.category11 path{fill:#9B5C9B}.epoch-theme-dark .epoch.category20b .bar.category11{fill:#9B5C9B}.epoch-theme-dark .epoch.category20b div.ref.category12{background-color:#7B487B}.epoch-theme-dark .epoch.category20b .category12 .line{stroke:#7B487B}.epoch-theme-dark .epoch.category20b .category12 .area,.epoch-theme-dark .epoch.category20b .category12 .dot{fill:#7B487B;stroke:transparent}.epoch-theme-dark .epoch.category20b .arc.category12 path{fill:#7B487B}.epoch-theme-dark .epoch.category20b .bar.category12{fill:#7B487B}.epoch-theme-dark .epoch.category20b div.ref.category13{background-color:#78E8D3}.epoch-theme-dark .epoch.category20b .category13 .line{stroke:#78E8D3}.epoch-theme-dark .epoch.category20b .category13 .area,.epoch-theme-dark .epoch.category20b .category13 .dot{fill:#78E8D3;stroke:transparent}.epoch-theme-dark .epoch.category20b .arc.category13 path{fill:#78E8D3}.epoch-theme-dark .epoch.category20b .bar.category13{fill:#78E8D3}.epoch-theme-dark .epoch.category20b div.ref.category14{background-color:#60BAAA}.epoch-theme-dark .epoch.category20b .category14 .line{stroke:#60BAAA}.epoch-theme-dark .epoch.category20b .category14 .area,.epoch-theme-dark .epoch.category20b .category14 .dot{fill:#60BAAA;stroke:transparent}.epoch-theme-dark .epoch.category20b .arc.category14 path{fill:#60BAAA}.epoch-theme-dark .epoch.category20b .bar.category14{fill:#60BAAA}.epoch-theme-dark .epoch.category20b div.ref.category15{background-color:#509B8D}.epoch-theme-dark .epoch.category20b .category15 .line{stroke:#509B8D}.epoch-theme-dark .epoch.category20b .category15 .area,.epoch-theme-dark .epoch.category20b .category15 .dot{fill:#509B8D;stroke:transparent}.epoch-theme-dark .epoch.category20b .arc.category15 path{fill:#509B8D}.epoch-theme-dark .epoch.category20b .bar.category15{fill:#509B8D}.epoch-theme-dark .epoch.category20b div.ref.category16{background-color:#3F7B70}.epoch-theme-dark .epoch.category20b .category16 .line{stroke:#3F7B70}.epoch-theme-dark .epoch.category20b .category16 .area,.epoch-theme-dark .epoch.category20b .category16 .dot{fill:#3F7B70;stroke:transparent}.epoch-theme-dark .epoch.category20b .arc.category16 path{fill:#3F7B70}.epoch-theme-dark .epoch.category20b .bar.category16{fill:#3F7B70}.epoch-theme-dark .epoch.category20b div.ref.category17{background-color:#C2FF97}.epoch-theme-dark .epoch.category20b .category17 .line{stroke:#C2FF97}.epoch-theme-dark .epoch.category20b .category17 .area,.epoch-theme-dark .epoch.category20b .category17 .dot{fill:#C2FF97;stroke:transparent}.epoch-theme-dark .epoch.category20b .arc.category17 path{fill:#C2FF97}.epoch-theme-dark .epoch.category20b .bar.category17{fill:#C2FF97}.epoch-theme-dark .epoch.category20b div.ref.category18{background-color:#9FD17C}.epoch-theme-dark .epoch.category20b .category18 .line{stroke:#9FD17C}.epoch-theme-dark .epoch.category20b .category18 .area,.epoch-theme-dark .epoch.category20b .category18 .dot{fill:#9FD17C;stroke:transparent}.epoch-theme-dark .epoch.category20b .arc.category18 path{fill:#9FD17C}.epoch-theme-dark .epoch.category20b .bar.category18{fill:#9FD17C}.epoch-theme-dark .epoch.category20b div.ref.category19{background-color:#7DA361}.epoch-theme-dark .epoch.category20b .category19 .line{stroke:#7DA361}.epoch-theme-dark .epoch.category20b .category19 .area,.epoch-theme-dark .epoch.category20b .category19 .dot{fill:#7DA361;stroke:transparent}.epoch-theme-dark .epoch.category20b .arc.category19 path{fill:#7DA361}.epoch-theme-dark .epoch.category20b .bar.category19{fill:#7DA361}.epoch-theme-dark .epoch.category20b div.ref.category20{background-color:#65854E}.epoch-theme-dark .epoch.category20b .category20 .line{stroke:#65854E}.epoch-theme-dark .epoch.category20b .category20 .area,.epoch-theme-dark .epoch.category20b .category20 .dot{fill:#65854E;stroke:transparent}.epoch-theme-dark .epoch.category20b .arc.category20 path{fill:#65854E}.epoch-theme-dark .epoch.category20b .bar.category20{fill:#65854E}.epoch-theme-dark .epoch.category20c div.ref.category1{background-color:#B7BCD1}.epoch-theme-dark .epoch.category20c .category1 .line{stroke:#B7BCD1}.epoch-theme-dark .epoch.category20c .category1 .area,.epoch-theme-dark .epoch.category20c .category1 .dot{fill:#B7BCD1;stroke:transparent}.epoch-theme-dark .epoch.category20c .arc.category1 path{fill:#B7BCD1}.epoch-theme-dark .epoch.category20c .bar.category1{fill:#B7BCD1}.epoch-theme-dark .epoch.category20c div.ref.category2{background-color:#979DAD}.epoch-theme-dark .epoch.category20c .category2 .line{stroke:#979DAD}.epoch-theme-dark .epoch.category20c .category2 .area,.epoch-theme-dark .epoch.category20c .category2 .dot{fill:#979DAD;stroke:transparent}.epoch-theme-dark .epoch.category20c .arc.category2 path{fill:#979DAD}.epoch-theme-dark .epoch.category20c .bar.category2{fill:#979DAD}.epoch-theme-dark .epoch.category20c div.ref.category3{background-color:#6E717D}.epoch-theme-dark .epoch.category20c .category3 .line{stroke:#6E717D}.epoch-theme-dark .epoch.category20c .category3 .area,.epoch-theme-dark .epoch.category20c .category3 .dot{fill:#6E717D;stroke:transparent}.epoch-theme-dark .epoch.category20c .arc.category3 path{fill:#6E717D}.epoch-theme-dark .epoch.category20c .bar.category3{fill:#6E717D}.epoch-theme-dark .epoch.category20c div.ref.category4{background-color:#595C66}.epoch-theme-dark .epoch.category20c .category4 .line{stroke:#595C66}.epoch-theme-dark .epoch.category20c .category4 .area,.epoch-theme-dark .epoch.category20c .category4 .dot{fill:#595C66;stroke:transparent}.epoch-theme-dark .epoch.category20c .arc.category4 path{fill:#595C66}.epoch-theme-dark .epoch.category20c .bar.category4{fill:#595C66}.epoch-theme-dark .epoch.category20c div.ref.category5{background-color:#FF857F}.epoch-theme-dark .epoch.category20c .category5 .line{stroke:#FF857F}.epoch-theme-dark .epoch.category20c .category5 .area,.epoch-theme-dark .epoch.category20c .category5 .dot{fill:#FF857F;stroke:transparent}.epoch-theme-dark .epoch.category20c .arc.category5 path{fill:#FF857F}.epoch-theme-dark .epoch.category20c .bar.category5{fill:#FF857F}.epoch-theme-dark .epoch.category20c div.ref.category6{background-color:#DE746E}.epoch-theme-dark .epoch.category20c .category6 .line{stroke:#DE746E}.epoch-theme-dark .epoch.category20c .category6 .area,.epoch-theme-dark .epoch.category20c .category6 .dot{fill:#DE746E;stroke:transparent}.epoch-theme-dark .epoch.category20c .arc.category6 path{fill:#DE746E}.epoch-theme-dark .epoch.category20c .bar.category6{fill:#DE746E}.epoch-theme-dark .epoch.category20c div.ref.category7{background-color:#B55F5A}.epoch-theme-dark .epoch.category20c .category7 .line{stroke:#B55F5A}.epoch-theme-dark .epoch.category20c .category7 .area,.epoch-theme-dark .epoch.category20c .category7 .dot{fill:#B55F5A;stroke:transparent}.epoch-theme-dark .epoch.category20c .arc.category7 path{fill:#B55F5A}.epoch-theme-dark .epoch.category20c .bar.category7{fill:#B55F5A}.epoch-theme-dark .epoch.category20c div.ref.category8{background-color:#964E4B}.epoch-theme-dark .epoch.category20c .category8 .line{stroke:#964E4B}.epoch-theme-dark .epoch.category20c .category8 .area,.epoch-theme-dark .epoch.category20c .category8 .dot{fill:#964E4B;stroke:transparent}.epoch-theme-dark .epoch.category20c .arc.category8 path{fill:#964E4B}.epoch-theme-dark .epoch.category20c .bar.category8{fill:#964E4B}.epoch-theme-dark .epoch.category20c div.ref.category9{background-color:#F3DE88}.epoch-theme-dark .epoch.category20c .category9 .line{stroke:#F3DE88}.epoch-theme-dark .epoch.category20c .category9 .area,.epoch-theme-dark .epoch.category20c .category9 .dot{fill:#F3DE88;stroke:transparent}.epoch-theme-dark .epoch.category20c .arc.category9 path{fill:#F3DE88}.epoch-theme-dark .epoch.category20c .bar.category9{fill:#F3DE88}.epoch-theme-dark .epoch.category20c div.ref.category10{background-color:#DBC87B}.epoch-theme-dark .epoch.category20c .category10 .line{stroke:#DBC87B}.epoch-theme-dark .epoch.category20c .category10 .area,.epoch-theme-dark .epoch.category20c .category10 .dot{fill:#DBC87B;stroke:transparent}.epoch-theme-dark .epoch.category20c .arc.category10 path{fill:#DBC87B}.epoch-theme-dark .epoch.category20c .bar.category10{fill:#DBC87B}.epoch-theme-dark .epoch.category20c div.ref.category11{background-color:#BAAA68}.epoch-theme-dark .epoch.category20c .category11 .line{stroke:#BAAA68}.epoch-theme-dark .epoch.category20c .category11 .area,.epoch-theme-dark .epoch.category20c .category11 .dot{fill:#BAAA68;stroke:transparent}.epoch-theme-dark .epoch.category20c .arc.category11 path{fill:#BAAA68}.epoch-theme-dark .epoch.category20c .bar.category11{fill:#BAAA68}.epoch-theme-dark .epoch.category20c div.ref.category12{background-color:#918551}.epoch-theme-dark .epoch.category20c .category12 .line{stroke:#918551}.epoch-theme-dark .epoch.category20c .category12 .area,.epoch-theme-dark .epoch.category20c .category12 .dot{fill:#918551;stroke:transparent}.epoch-theme-dark .epoch.category20c .arc.category12 path{fill:#918551}.epoch-theme-dark .epoch.category20c .bar.category12{fill:#918551}.epoch-theme-dark .epoch.category20c div.ref.category13{background-color:#C9935E}.epoch-theme-dark .epoch.category20c .category13 .line{stroke:#C9935E}.epoch-theme-dark .epoch.category20c .category13 .area,.epoch-theme-dark .epoch.category20c .category13 .dot{fill:#C9935E;stroke:transparent}.epoch-theme-dark .epoch.category20c .arc.category13 path{fill:#C9935E}.epoch-theme-dark .epoch.category20c .bar.category13{fill:#C9935E}.epoch-theme-dark .epoch.category20c div.ref.category14{background-color:#B58455}.epoch-theme-dark .epoch.category20c .category14 .line{stroke:#B58455}.epoch-theme-dark .epoch.category20c .category14 .area,.epoch-theme-dark .epoch.category20c .category14 .dot{fill:#B58455;stroke:transparent}.epoch-theme-dark .epoch.category20c .arc.category14 path{fill:#B58455}.epoch-theme-dark .epoch.category20c .bar.category14{fill:#B58455}.epoch-theme-dark .epoch.category20c div.ref.category15{background-color:#997048}.epoch-theme-dark .epoch.category20c .category15 .line{stroke:#997048}.epoch-theme-dark .epoch.category20c .category15 .area,.epoch-theme-dark .epoch.category20c .category15 .dot{fill:#997048;stroke:transparent}.epoch-theme-dark .epoch.category20c .arc.category15 path{fill:#997048}.epoch-theme-dark .epoch.category20c .bar.category15{fill:#997048}.epoch-theme-dark .epoch.category20c div.ref.category16{background-color:#735436}.epoch-theme-dark .epoch.category20c .category16 .line{stroke:#735436}.epoch-theme-dark .epoch.category20c .category16 .area,.epoch-theme-dark .epoch.category20c .category16 .dot{fill:#735436;stroke:transparent}.epoch-theme-dark .epoch.category20c .arc.category16 path{fill:#735436}.epoch-theme-dark .epoch.category20c .bar.category16{fill:#735436}.epoch-theme-dark .epoch.category20c div.ref.category17{background-color:#A488FF}.epoch-theme-dark .epoch.category20c .category17 .line{stroke:#A488FF}.epoch-theme-dark .epoch.category20c .category17 .area,.epoch-theme-dark .epoch.category20c .category17 .dot{fill:#A488FF;stroke:transparent}.epoch-theme-dark .epoch.category20c .arc.category17 path{fill:#A488FF}.epoch-theme-dark .epoch.category20c .bar.category17{fill:#A488FF}.epoch-theme-dark .epoch.category20c div.ref.category18{background-color:#8670D1}.epoch-theme-dark .epoch.category20c .category18 .line{stroke:#8670D1}.epoch-theme-dark .epoch.category20c .category18 .area,.epoch-theme-dark .epoch.category20c .category18 .dot{fill:#8670D1;stroke:transparent}.epoch-theme-dark .epoch.category20c .arc.category18 path{fill:#8670D1}.epoch-theme-dark .epoch.category20c .bar.category18{fill:#8670D1}.epoch-theme-dark .epoch.category20c div.ref.category19{background-color:#705CAD}.epoch-theme-dark .epoch.category20c .category19 .line{stroke:#705CAD}.epoch-theme-dark .epoch.category20c .category19 .area,.epoch-theme-dark .epoch.category20c .category19 .dot{fill:#705CAD;stroke:transparent}.epoch-theme-dark .epoch.category20c .arc.category19 path{fill:#705CAD}.epoch-theme-dark .epoch.category20c .bar.category19{fill:#705CAD}.epoch-theme-dark .epoch.category20c div.ref.category20{background-color:#52447F}.epoch-theme-dark .epoch.category20c .category20 .line{stroke:#52447F}.epoch-theme-dark .epoch.category20c .category20 .area,.epoch-theme-dark .epoch.category20c .category20 .dot{fill:#52447F;stroke:transparent}.epoch-theme-dark .epoch.category20c .arc.category20 path{fill:#52447F}.epoch-theme-dark .epoch.category20c .bar.category20{fill:#52447F}
diff --git a/modules/http/static/epoch.js b/modules/http/static/epoch.js
new file mode 100644
index 0000000..98fc691
--- /dev/null
+++ b/modules/http/static/epoch.js
@@ -0,0 +1,4 @@
+/* SPDX-License-Identifier: MIT */
+var base,base1,base2,base3;null==window.Epoch&&(window.Epoch={}),null==(base=window.Epoch).Chart&&(base.Chart={}),null==(base1=window.Epoch).Time&&(base1.Time={}),null==(base2=window.Epoch).Util&&(base2.Util={}),null==(base3=window.Epoch).Formats&&(base3.Formats={}),Epoch.warn=function(t){return(console.warn||console.log)("Epoch Warning: "+t)},Epoch.exception=function(t){throw"Epoch Error: "+t},Epoch.TestContext=function(){function t(){var t,n,e;for(this._log=[],t=0,n=i.length;n>t;t++)e=i[t],this._makeFauxMethod(e)}var i;return i=["arc","arcTo","beginPath","bezierCurveTo","clearRect","clip","closePath","drawImage","fill","fillRect","fillText","moveTo","quadraticCurveTo","rect","restore","rotate","save","scale","scrollPathIntoView","setLineDash","setTransform","stroke","strokeRect","strokeText","transform","translate","lineTo"],t.prototype._makeFauxMethod=function(t){return this[t]=function(){var i;return this._log.push(t+"("+function(){var t,n,e;for(e=[],t=0,n=arguments.length;n>t;t++)i=arguments[t],e.push(i.toString());return e}.apply(this,arguments).join(",")+")")}},t.prototype.getImageData=function(){var t;return this._log.push("getImageData("+function(){var i,n,e;for(e=[],i=0,n=arguments.length;n>i;i++)t=arguments[i],e.push(t.toString());return e}.apply(this,arguments).join(",")+")"),{width:0,height:0,resolution:1,data:[]}},t}();var ref,typeFunction,hasProp={}.hasOwnProperty;typeFunction=function(t){return function(i){return Object.prototype.toString.call(i)==="[object "+t+"]"}},Epoch.isArray=null!=(ref=Array.isArray)?ref:typeFunction("Array"),Epoch.isObject=typeFunction("Object"),Epoch.isString=typeFunction("String"),Epoch.isFunction=typeFunction("Function"),Epoch.isNumber=typeFunction("Number"),Epoch.isElement=function(t){return"undefined"!=typeof HTMLElement&&null!==HTMLElement?t instanceof HTMLElement:null!=t&&Epoch.isObject(t)&&1===t.nodeType&&Epoch.isString(t.nodeName)},Epoch.isNonEmptyArray=function(t){return Epoch.isArray(t)&&t.length>0},Epoch.Util.copy=function(t){var i,n,e;if(null==t)return null;i={};for(n in t)hasProp.call(t,n)&&(e=t[n],i[n]=e);return i},Epoch.Util.defaults=function(t,i){var n,e,r,o,s,a;s=Epoch.Util.copy(t);for(r in i)hasProp.call(i,r)&&(a=i[r],o=t[r],e=i[r],n=Epoch.isObject(o)&&Epoch.isObject(e),null!=o&&null!=e?n&&!Epoch.isArray(o)?s[r]=Epoch.Util.defaults(o,e):s[r]=o:null!=o?s[r]=o:s[r]=e);return s},Epoch.Util.formatSI=function(t,i,n){var e,r,o,s,a;if(null==i&&(i=1),null==n&&(n=!1),1e3>t)return s=t,((0|s)!==s||n)&&(s=s.toFixed(i)),s;a=["K","M","G","T","P","E","Z","Y"];for(r in a)if(hasProp.call(a,r)&&(o=a[r],e=Math.pow(10,3*((0|r)+1)),t>=e&&t<Math.pow(10,3*((0|r)+2))))return s=t/e,(s%1!==0||n)&&(s=s.toFixed(i)),s+" "+o},Epoch.Util.formatBytes=function(t,i,n){var e,r,o,s,a;if(null==i&&(i=1),null==n&&(n=!1),1024>t)return s=t,(s%1!==0||n)&&(s=s.toFixed(i)),s+" B";a=["KB","MB","GB","TB","PB","EB","ZB","YB"];for(r in a)if(hasProp.call(a,r)&&(o=a[r],e=Math.pow(1024,(0|r)+1),t>=e&&t<Math.pow(1024,(0|r)+2)))return s=t/e,(s%1!==0||n)&&(s=s.toFixed(i)),s+" "+o},Epoch.Util.dasherize=function(t){return Epoch.Util.trim(t).replace("\n","").replace(/\s+/g,"-").toLowerCase()},Epoch.Util.domain=function(t,i){var n,e,r,o,s,a,h,p,l;for(null==i&&(i="x"),l={},n=[],r=0,a=t.length;a>r;r++)for(s=t[r],p=s.values,o=0,h=p.length;h>o;o++)e=p[o],null==l[e[i]]&&(n.push(e[i]),l[e[i]]=!0);return n},Epoch.Util.trim=function(t){return Epoch.isString(t)?t.replace(/^\s+/g,"").replace(/\s+$/g,""):null},Epoch.Util.getComputedStyle=function(t,i){return Epoch.isFunction(window.getComputedStyle)?window.getComputedStyle(t,i):null!=t.currentStyle?t.currentStyle:void 0},Epoch.Util.toRGBA=function(t,i){var n,e,r,o,s,a,h;return(o=t.match(/^rgba\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*[0-9\.]+\)/))?(n=o[0],s=o[1],r=o[2],e=o[3],a="rgba("+s+","+r+","+e+","+i+")"):(h=d3.rgb(t))&&(a="rgba("+h.r+","+h.g+","+h.b+","+i+")"),a},Epoch.Util.getContext=function(t,i){return null==i&&(i="2d"),t.getContext(i)},Epoch.Events=function(){function t(){this._events={}}return t.prototype.on=function(t,i){var n;if(null!=i)return null==(n=this._events)[t]&&(n[t]=[]),this._events[t].push(i)},t.prototype.onAll=function(t){var i,n,e;if(Epoch.isObject(t)){e=[];for(n in t)hasProp.call(t,n)&&(i=t[n],e.push(this.on(n,i)));return e}},t.prototype.off=function(t,i){var n,e;if(Epoch.isArray(this._events[t])){if(null==i)return delete this._events[t];for(e=[];(n=this._events[t].indexOf(i))>=0;)e.push(this._events[t].splice(n,1));return e}},t.prototype.offAll=function(t){var i,n,e,r,o,s;if(Epoch.isArray(t)){for(o=[],n=0,e=t.length;e>n;n++)r=t[n],o.push(this.off(r));return o}if(Epoch.isObject(t)){s=[];for(r in t)hasProp.call(t,r)&&(i=t[r],s.push(this.off(r,i)));return s}},t.prototype.trigger=function(t){var i,n,e,r,o,s,a,h;if(null!=this._events[t]){for(i=function(){var t,i,n;for(n=[],r=t=1,i=arguments.length;i>=1?i>t:t>i;r=i>=1?++t:--t)n.push(arguments[r]);return n}.apply(this,arguments),a=this._events[t],h=[],o=0,s=a.length;s>o;o++)n=a[o],e=null,Epoch.isString(n)?e=this[n]:Epoch.isFunction(n)&&(e=n),null==e&&Epoch.exception("Callback for event '"+t+"' is not a function or reference to a method."),h.push(e.apply(this,i));return h}},t}(),Epoch.Util.flatten=function(t){var i,n,e,r,o,s,a;if(!Array.isArray(t))throw new Error("Epoch.Util.flatten only accepts arrays");for(a=[],e=0,o=t.length;o>e;e++)if(i=t[e],Array.isArray(i))for(r=0,s=i.length;s>r;r++)n=i[r],a.push(n);else a.push(i);return a},d3.selection.prototype.width=function(t){return null!=t&&Epoch.isString(t)?this.style("width",t):null!=t&&Epoch.isNumber(t)?this.style("width",t+"px"):+Epoch.Util.getComputedStyle(this.node(),null).width.replace("px","")},d3.selection.prototype.height=function(t){return null!=t&&Epoch.isString(t)?this.style("height",t):null!=t&&Epoch.isNumber(t)?this.style("height",t+"px"):+Epoch.Util.getComputedStyle(this.node(),null).height.replace("px","")};var d3Seconds;Epoch.Formats.regular=function(t){return t},Epoch.Formats.si=function(t){return Epoch.Util.formatSI(t)},Epoch.Formats.percent=function(t){return(100*t).toFixed(1)+"%"},Epoch.Formats.seconds=function(t){return d3Seconds(new Date(1e3*t))},d3Seconds=d3.time.format("%I:%M:%S %p"),Epoch.Formats.bytes=function(t){return Epoch.Util.formatBytes(t)};var extend=function(t,i){function n(){this.constructor=t}for(var e in i)hasProp.call(i,e)&&(t[e]=i[e]);return n.prototype=i.prototype,t.prototype=new n,t.__super__=i.prototype,t},hasProp={}.hasOwnProperty;Epoch.Chart.Base=function(t){function i(t){this.options=null!=t?t:{},i.__super__.constructor.call(this),this.options.model?(null!=this.options.model.hasData()?this.setData(this.options.model.getData(this.options.type,this.options.dataFormat)):this.setData(this.options.data||[]),this.options.model.on("data:updated",function(t){return function(){return t.setDataFromModel()}}(this))):this.setData(this.options.data||[]),null!=this.options.el&&(this.el=d3.select(this.options.el)),this.width=this.options.width,this.height=this.options.height,null!=this.el?(null==this.width&&(this.width=this.el.width()),null==this.height&&(this.height=this.el.height())):(null==this.width&&(this.width=n.width),null==this.height&&(this.height=n.height),this.el=d3.select(document.createElement("DIV")).attr("width",this.width).attr("height",this.height)),this.onAll(e)}var n,e;return extend(i,t),n={width:320,height:240,dataFormat:null},e={"option:width":"dimensionsChanged","option:height":"dimensionsChanged","layer:shown":"layerChanged","layer:hidden":"layerChanged"},i.prototype._getAllOptions=function(){return Epoch.Util.defaults({},this.options)},i.prototype._getOption=function(t){var i,n,e;for(i=t.split("."),n=this.options;i.length&&null!=n;)e=i.shift(),n=n[e];return n},i.prototype._setOption=function(t,i){var n,e,r;for(n=t.split("."),e=this.options;n.length;){if(r=n.shift(),0===n.length)return e[r]=arguments[1],void this.trigger("option:"+arguments[0]);null==e[r]&&(e[r]={}),e=e[r]}},i.prototype._setManyOptions=function(t,i){var n,e,r;null==i&&(i=""),e=[];for(n in t)hasProp.call(t,n)&&(r=t[n],Epoch.isObject(r)?e.push(this._setManyOptions(r,i+n+".")):e.push(this._setOption(i+n,r)));return e},i.prototype.option=function(){return 0===arguments.length?this._getAllOptions():1===arguments.length&&Epoch.isString(arguments[0])?this._getOption(arguments[0]):2===arguments.length&&Epoch.isString(arguments[0])?this._setOption(arguments[0],arguments[1]):1===arguments.length&&Epoch.isObject(arguments[0])?this._setManyOptions(arguments[0]):void 0},i.prototype.setDataFromModel=function(){var t;return t=this._prepareData(this.options.model.getData(this.options.type,this.options.dataFormat)),this.data=this._annotateLayers(t),this.draw()},i.prototype.setData=function(t,i){var n;return null==i&&(i={}),n=this._prepareData(this.rawData=this._formatData(t)),this.data=this._annotateLayers(n)},i.prototype._prepareData=function(t){return t},i.prototype._formatData=function(t){return Epoch.Data.formatData(t,this.options.type,this.options.dataFormat)},i.prototype._annotateLayers=function(t){var i,n,e,r,o;for(i=1,e=0,o=t.length;o>e;e++)r=t[e],n=["layer"],n.push("category"+i),r.category=i,r.visible=!0,null!=r.label&&n.push(Epoch.Util.dasherize(r.label)),r.className=n.join(" "),i++;return t},i.prototype._findLayer=function(t){var i,n,e,r,o,s;if(r=null,Epoch.isString(t)){for(s=this.data,i=0,o=s.length;o>i;i++)if(e=s[i],e.label===t){r=e;break}}else Epoch.isNumber(t)&&(n=parseInt(t),0>n||n>=this.data.length||(r=this.data[n]));return r},i.prototype.showLayer=function(t){var i;if((i=this._findLayer(t))&&!i.visible)return i.visible=!0,this.trigger("layer:shown")},i.prototype.hideLayer=function(t){var i;if((i=this._findLayer(t))&&i.visible)return i.visible=!1,this.trigger("layer:hidden")},i.prototype.toggleLayer=function(t){var i;if(i=this._findLayer(t))return i.visible=!i.visible,i.visible?this.trigger("layer:shown"):this.trigger("layer:hidden")},i.prototype.isLayerVisible=function(t){var i;return(i=this._findLayer(t))?i.visible:null},i.prototype.getVisibleLayers=function(){return this.data.filter(function(t){return t.visible})},i.prototype.update=function(t,i){return null==i&&(i=!0),this.setData(t),i?this.draw():void 0},i.prototype.draw=function(){return this.trigger("draw")},i.prototype._getScaleDomain=function(t){var i,n,e,r;return Array.isArray(t)?t:Epoch.isString(t)&&(i=this.getVisibleLayers().filter(function(i){return i.range===t}).map(function(t){return t.values}),null!=i&&i.length)?(r=Epoch.Util.flatten(i).map(function(t){return t.y}),e=function(t,i){return t>i?i:t},n=function(t,i){return i>t?i:t},[r.reduce(e,r[0]),r.reduce(n,r[0])]):Array.isArray(this.options.range)?this.options.range:this.options.range&&Array.isArray(this.options.range.left)?this.options.range.left:this.options.range&&Array.isArray(this.options.range.right)?this.options.range.right:this.extent(function(t){return t.y})},i.prototype.extent=function(t){return[d3.min(this.getVisibleLayers(),function(i){return d3.min(i.values,t)}),d3.max(this.getVisibleLayers(),function(i){return d3.max(i.values,t)})]},i.prototype.dimensionsChanged=function(){return this.width=this.option("width")||this.width,this.height=this.option("height")||this.height,this.el.width(this.width),this.el.height(this.height)},i.prototype.layerChanged=function(){return this.draw()},i}(Epoch.Events),Epoch.Chart.SVG=function(t){function i(t){this.options=null!=t?t:{},i.__super__.constructor.call(this,this.options),null!=this.el?this.svg=this.el.append("svg"):this.svg=d3.select(document.createElement("svg")),this.svg.attr({xmlns:"http://www.w3.org/2000/svg",width:this.width,height:this.height})}return extend(i,t),i.prototype.dimensionsChanged=function(){return i.__super__.dimensionsChanged.call(this),this.svg.attr("width",this.width).attr("height",this.height)},i}(Epoch.Chart.Base),Epoch.Chart.Canvas=function(t){function i(t){this.options=null!=t?t:{},i.__super__.constructor.call(this,this.options),null!=this.options.pixelRatio?this.pixelRatio=this.options.pixelRatio:null!=window.devicePixelRatio?this.pixelRatio=window.devicePixelRatio:this.pixelRatio=1,this.canvas=d3.select(document.createElement("CANVAS")),this.canvas.style({width:this.width+"px",height:this.height+"px"}),this.canvas.attr({width:this.getWidth(),height:this.getHeight()}),null!=this.el&&this.el.node().appendChild(this.canvas.node()),this.ctx=Epoch.Util.getContext(this.canvas.node())}return extend(i,t),i.prototype.getWidth=function(){return this.width*this.pixelRatio},i.prototype.getHeight=function(){return this.height*this.pixelRatio},i.prototype.clear=function(){return this.ctx.clearRect(0,0,this.getWidth(),this.getHeight())},i.prototype.getStyles=function(t){return Epoch.QueryCSS.getStyles(t,this.el)},i.prototype.dimensionsChanged=function(){return i.__super__.dimensionsChanged.call(this),this.canvas.style({width:this.width+"px",height:this.height+"px"}),this.canvas.attr({width:this.getWidth(),height:this.getHeight()})},i.prototype.redraw=function(){return Epoch.QueryCSS.purge(),this.draw()},i}(Epoch.Chart.Base);var QueryCSS;QueryCSS=function(){function t(){}var i,n,e,r,o,s,a;return e="_canvas_css_reference",i="data-epoch-container-id",r=0,s=function(){return"epoch-container-"+r++},n=/^([^#. ]+)?(#[^. ]+)?(\.[^# ]+)?$/,o=!1,a=function(t){var i,e,r,o,s,a;return o=t.match(n),null==o?Epoch.error("Query CSS cannot match given selector: "+t):(a=o[0],s=o[1],r=o[2],i=o[3],s=(null!=s?s:"div").toUpperCase(),e=document.createElement(s),null!=r&&(e.id=r.substr(1)),null!=i&&(e.className=i.substr(1).replace(/\./g," ")),e)},t.log=function(t){return o=t},t.cache={},t.styleList=["fill","stroke","stroke-width"],t.container=null,t.purge=function(){return t.cache={}},t.getContainer=function(){var i;return null!=t.container?t.container:(i=document.createElement("DIV"),i.id=e,document.body.appendChild(i),t.container=d3.select(i))},t.hash=function(t,n){var e;return e=n.attr(i),null==e&&(e=s(),n.attr(i,e)),e+"__"+t},t.getStyles=function(i,n){var r,s,h,p,l,u,c,g,d,f,y,m,v,x,_,w,k,b,E,C,A,S;if(s=t.hash(i,n),r=t.cache[s],null!=r)return r;for(x=[],v=n.node().parentNode;null!=v&&"body"!==v.nodeName.toLowerCase();)x.unshift(v),v=v.parentNode;for(x.push(n.node()),C=[],l=0,g=x.length;g>l;l++)p=x[l],E=p.nodeName.toLowerCase(),null!=p.id&&p.id.length>0&&(E+="#"+p.id),null!=p.className&&p.className.length>0&&(E+="."+Epoch.Util.trim(p.className).replace(/\s+/g,".")),C.push(E);for(C.push("svg"),w=Epoch.Util.trim(i).split(/\s+/),u=0,d=w.length;d>u;u++)S=w[u],C.push(S);for(o&&console.log(C),m=b=a(C.shift());C.length;)h=a(C.shift()),m.appendChild(h),m=h;for(o&&console.log(b),t.getContainer().node().appendChild(b),_=d3.select("#"+e+" "+i),A={},k=t.styleList,c=0,f=k.length;f>c;c++)y=k[c],A[y]=_.style(y);return t.cache[s]=A,t.getContainer().html(""),A},t}(),Epoch.QueryCSS=QueryCSS;var applyLayerLabel,base,hasProp={}.hasOwnProperty,slice=[].slice;null==Epoch.Data&&(Epoch.Data={}),null==(base=Epoch.Data).Format&&(base.Format={}),applyLayerLabel=function(t,i,n,e){var r,o,s,a,h;if(null==e&&(e=[]),h=[i.labels,i.autoLabels,i.keyLabels],a=h[0],r=h[1],o=h[2],null!=a&&Epoch.isArray(a)&&a.length>n)t.label=a[n];else if(o&&e.length>n)t.label=e[n];else if(r){for(s=[];n>=0;)s.push(String.fromCharCode(65+n%26)),n-=26;t.label=s.join("")}return t},Epoch.Data.Format.array=function(){var t,i,n,e,r,o,s;return i={x:function(t,i){return i},y:function(t,i){return t},time:function(t,i,n){return parseInt(n)+parseInt(i)},type:"area",autoLabels:!1,labels:[],startTime:parseInt((new Date).getTime()/1e3)},t=function(t,i,n){var e,r,o;if(r=[],Epoch.isArray(t[0]))for(e in t)hasProp.call(t,e)&&(o=t[e],r.push(applyLayerLabel({values:o.map(n)},i,parseInt(e))));else r.push(applyLayerLabel({values:t.map(n)},i,0));return r},e=function(i,n){return t(i,n,function(t,i){return{x:n.x(t,i),y:n.y(t,i)}})},s=function(i,n){return t(i,n,function(t,i){return{time:n.time(t,i,n.startTime),y:n.y(t,i)}})},r=function(i,n){return t(i,n,function(t,i){return{time:n.time(t,i,n.startTime),histogram:t}})},o=function(t,i){var n,e,r;e=[];for(n in t)if(hasProp.call(t,n)){if(r=t[n],!Epoch.isNumber(t[0]))return[];e.push(applyLayerLabel({value:r},i,n))}return e},n=function(t,n){var a;return null==t&&(t=[]),null==n&&(n={}),Epoch.isNonEmptyArray(t)?(a=Epoch.Util.defaults(n,i),"time.heatmap"===a.type?r(t,a):a.type.match(/^time\./)?s(t,a):"pie"===a.type?o(t,a):e(t,a)):[]},n.entry=function(t,e){var r,o,s,a,h,p,l,u;if(null==e&&(e={}),"time.gauge"===e.type)return null==t?0:(p=Epoch.Util.defaults(e,i),r=Epoch.isArray(t)?t[0]:t,p.y(r,0));if(null==t)return[];for(null==e.startTime&&(e.startTime=parseInt((new Date).getTime()/1e3)),o=Epoch.isArray(t)?t.map(function(t){return[t]}):[t],l=n(o,e),u=[],s=0,h=l.length;h>s;s++)a=l[s],u.push(a.values[0]);return u},n}(),Epoch.Data.Format.tuple=function(){var t,i,n;return i={x:function(t,i){return t},y:function(t,i){return t},time:function(t,i){return t},type:"area",autoLabels:!1,labels:[]},t=function(t,i,n){var e,r,o;if(!Epoch.isArray(t[0]))return[];if(r=[],Epoch.isArray(t[0][0]))for(e in t)hasProp.call(t,e)&&(o=t[e],r.push(applyLayerLabel({values:o.map(n)},i,parseInt(e))));else r.push(applyLayerLabel({values:t.map(n)},i,0));return r},n=function(n,e){var r;return null==n&&(n=[]),null==e&&(e={}),Epoch.isNonEmptyArray(n)?(r=Epoch.Util.defaults(e,i),"pie"===r.type||"time.heatmap"===r.type||"time.gauge"===r.type?[]:r.type.match(/^time\./)?t(n,r,function(t,i){return{time:r.time(t[0],parseInt(i)),y:r.y(t[1],parseInt(i))}}):t(n,r,function(t,i){return{x:r.x(t[0],parseInt(i)),y:r.y(t[1],parseInt(i))}})):[]},n.entry=function(t,i){var e,r,o,s,a,h;if(null==i&&(i={}),null==t)return[];for(null==i.startTime&&(i.startTime=parseInt((new Date).getTime()/1e3)),e=Epoch.isArray(t)&&Epoch.isArray(t[0])?t.map(function(t){return[t]}):[t],a=n(e,i),h=[],r=0,s=a.length;s>r;r++)o=a[r],h.push(o.values[0]);return h},n}(),Epoch.Data.Format.keyvalue=function(){var t,i,n,e,r;return i={type:"area",x:function(t,i){return parseInt(i)},y:function(t,i){return t},time:function(t,i,n){return parseInt(n)+parseInt(i)},labels:[],autoLabels:!1,keyLabels:!0,startTime:parseInt((new Date).getTime()/1e3)},t=function(t,i,n,e){var r,o,s,a,h,p;h=[];for(s in i)if(hasProp.call(i,s)){a=i[s],p=[];for(o in t)hasProp.call(t,o)&&(r=t[o],p.push(e(r,a,parseInt(o))));h.push(applyLayerLabel({values:p},n,parseInt(s),i))}return h},e=function(i,n,e){return t(i,n,e,function(t,i,n){var r;return r=Epoch.isString(e.x)?t[e.x]:e.x(t,parseInt(n)),{x:r,y:e.y(t[i],parseInt(n))}})},r=function(i,n,e,r){return null==r&&(r="y"),t(i,n,e,function(t,i,n){var o;return o=Epoch.isString(e.time)?{time:t[e.time]}:{time:e.time(t,parseInt(n),e.startTime)},o[r]=e.y(t[i],parseInt(n)),o})},n=function(t,n,o){var s;return null==t&&(t=[]),null==n&&(n=[]),null==o&&(o={}),Epoch.isNonEmptyArray(t)&&Epoch.isNonEmptyArray(n)?(s=Epoch.Util.defaults(o,i),"pie"===s.type||"time.gauge"===s.type?[]:"time.heatmap"===s.type?r(t,n,s,"histogram"):s.type.match(/^time\./)?r(t,n,s):e(t,n,s)):[]},n.entry=function(t,i,e){var r,o,s,a,h;if(null==i&&(i=[]),null==e&&(e={}),null==t||!Epoch.isNonEmptyArray(i))return[];for(null==e.startTime&&(e.startTime=parseInt((new Date).getTime()/1e3)),a=n([t],i,e),h=[],r=0,s=a.length;s>r;r++)o=a[r],h.push(o.values[0]);return h},n}(),Epoch.data=function(){var t,i,n;return n=arguments[0],t=2<=arguments.length?slice.call(arguments,1):[],null==(i=Epoch.Data.Format[n])?[]:i.apply(i,t)},Epoch.Data.formatData=function(t,i,n){var e,r,o,s,a,h;if(null==t&&(t=[]),!Epoch.isNonEmptyArray(t))return t;if(Epoch.isString(n))return a={type:i},Epoch.data(n,t,a);if(!Epoch.isObject(n))return t;if(null==n.name||!Epoch.isString(n.name))return t;if(null==Epoch.Data.Format[n.name])return t;if(r=[n.name,t],null!=n.arguments&&Epoch.isArray(n.arguments))for(h=n.arguments,o=0,s=h.length;s>o;o++)e=h[o],r.push(e);return null!=n.options?(a=n.options,null!=i&&null==a.type&&(a.type=i),r.push(a)):null!=i&&r.push({type:i}),Epoch.data.apply(Epoch.data,r)},Epoch.Data.formatEntry=function(t,i,n){var e,r,o,s,a,h,p,l;if(null==n)return t;if(Epoch.isString(n))return p={type:i},Epoch.Data.Format[n].entry(t,p);if(!Epoch.isObject(n))return t;if(null==n.name||!Epoch.isString(n.name))return t;if(null==Epoch.Data.Format[n.name])return t;if(o=Epoch.Util.defaults(n,{}),r=[t],null!=o.arguments&&Epoch.isArray(o.arguments))for(l=o.arguments,a=0,h=l.length;h>a;a++)e=l[a],r.push(e);return null!=o.options?(p=o.options,p.type=i,r.push(p)):null!=i&&r.push({type:i}),s=Epoch.Data.Format[o.name].entry,s.apply(s,r)};var extend=function(t,i){function n(){this.constructor=t}for(var e in i)hasProp.call(i,e)&&(t[e]=i[e]);return n.prototype=i.prototype,t.prototype=new n,t.__super__=i.prototype,t},hasProp={}.hasOwnProperty;Epoch.Model=function(t){function i(t){null==t&&(t={}),i.__super__.constructor.call(this),t=Epoch.Util.defaults(t,n),this.dataFormat=t.dataFormat,this.data=t.data,this.loading=!1}var n;return extend(i,t),n={dataFormat:null},i.prototype.setData=function(t){return this.data=t,this.trigger("data:updated")},i.prototype.push=function(t){return this.entry=t,this.trigger("data:push")},i.prototype.hasData=function(){return null!=this.data},i.prototype.getData=function(t,i){return null==i&&(i=this.dataFormat),Epoch.Data.formatData(this.data,t,i)},i.prototype.getNext=function(t,i){return null==i&&(i=this.dataFormat),Epoch.Data.formatEntry(this.entry,t,i)},i}(Epoch.Events);var extend=function(t,i){function n(){this.constructor=t}for(var e in i)hasProp.call(i,e)&&(t[e]=i[e]);return n.prototype=i.prototype,t.prototype=new n,t.__super__=i.prototype,t},hasProp={}.hasOwnProperty;Epoch.Chart.Plot=function(t){function i(t){var o,s,a,h,p;for(this.options=null!=t?t:{},o=Epoch.Util.copy(this.options.margins)||{},i.__super__.constructor.call(this,this.options=Epoch.Util.defaults(this.options,e)),this.margins={},p=["top","right","bottom","left"],s=0,a=p.length;a>s;s++)h=p[s],this.margins[h]=null!=this.options.margins&&null!=this.options.margins[h]?this.options.margins[h]:this.hasAxis(h)?n[h]:6;this.g=this.svg.append("g").attr("transform","translate("+this.margins.left+", "+this.margins.top+")"),this.onAll(r)}var n,e,r;return extend(i,t),e={domain:null,range:null,axes:["left","bottom"],ticks:{top:14,bottom:14,left:5,right:5},tickFormats:{top:Epoch.Formats.regular,bottom:Epoch.Formats.regular,left:Epoch.Formats.si,right:Epoch.Formats.si}},n={top:25,right:50,bottom:25,left:50},r={"option:margins.top":"marginsChanged","option:margins.right":"marginsChanged","option:margins.bottom":"marginsChanged","option:margins.left":"marginsChanged","option:axes":"axesChanged","option:ticks.top":"ticksChanged","option:ticks.right":"ticksChanged","option:ticks.bottom":"ticksChanged","option:ticks.left":"ticksChanged","option:tickFormats.top":"tickFormatsChanged","option:tickFormats.right":"tickFormatsChanged","option:tickFormats.bottom":"tickFormatsChanged","option:tickFormats.left":"tickFormatsChanged","option:domain":"domainChanged","option:range":"rangeChanged"},i.prototype.setTickFormat=function(t,i){return this.options.tickFormats[t]=i},i.prototype.hasAxis=function(t){return this.options.axes.indexOf(t)>-1},i.prototype.innerWidth=function(){return this.width-(this.margins.left+this.margins.right)},i.prototype.innerHeight=function(){return this.height-(this.margins.top+this.margins.bottom)},i.prototype.x=function(){var t,i;return t=null!=(i=this.options.domain)?i:this.extent(function(t){return t.x}),d3.scale.linear().domain(t).range([0,this.innerWidth()])},i.prototype.y=function(t){return d3.scale.linear().domain(this._getScaleDomain(t)).range([this.innerHeight(),0])},i.prototype.bottomAxis=function(){return d3.svg.axis().scale(this.x()).orient("bottom").ticks(this.options.ticks.bottom).tickFormat(this.options.tickFormats.bottom)},i.prototype.topAxis=function(){return d3.svg.axis().scale(this.x()).orient("top").ticks(this.options.ticks.top).tickFormat(this.options.tickFormats.top)},i.prototype.leftAxis=function(){var t;return t=this.options.range?this.options.range.left:null,d3.svg.axis().scale(this.y(t)).orient("left").ticks(this.options.ticks.left).tickFormat(this.options.tickFormats.left)},i.prototype.rightAxis=function(){var t;return t=this.options.range?this.options.range.right:null,d3.svg.axis().scale(this.y(t)).orient("right").ticks(this.options.ticks.right).tickFormat(this.options.tickFormats.right)},i.prototype.draw=function(){return this._axesDrawn?this._redrawAxes():this._drawAxes(),i.__super__.draw.call(this)},i.prototype._redrawAxes=function(){return this.hasAxis("bottom")&&this.g.selectAll(".x.axis.bottom").transition().duration(500).ease("linear").call(this.bottomAxis()),this.hasAxis("top")&&this.g.selectAll(".x.axis.top").transition().duration(500).ease("linear").call(this.topAxis()),this.hasAxis("left")&&this.g.selectAll(".y.axis.left").transition().duration(500).ease("linear").call(this.leftAxis()),this.hasAxis("right")?this.g.selectAll(".y.axis.right").transition().duration(500).ease("linear").call(this.rightAxis()):void 0},i.prototype._drawAxes=function(){return this.hasAxis("bottom")&&this.g.append("g").attr("class","x axis bottom").attr("transform","translate(0, "+this.innerHeight()+")").call(this.bottomAxis()),this.hasAxis("top")&&this.g.append("g").attr("class","x axis top").call(this.topAxis()),this.hasAxis("left")&&this.g.append("g").attr("class","y axis left").call(this.leftAxis()),this.hasAxis("right")&&this.g.append("g").attr("class","y axis right").attr("transform","translate("+this.innerWidth()+", 0)").call(this.rightAxis()),this._axesDrawn=!0},i.prototype.dimensionsChanged=function(){return i.__super__.dimensionsChanged.call(this),this.g.selectAll(".axis").remove(),this._axesDrawn=!1,this.draw()},i.prototype.marginsChanged=function(){var t,i,n;if(null!=this.options.margins){i=this.options.margins;for(t in i)hasProp.call(i,t)&&(n=i[t],null==n?this.margins[t]=6:this.margins[t]=n);return this.g.transition().duration(750).attr("transform","translate("+this.margins.left+", "+this.margins.top+")"),this.draw()}},i.prototype.axesChanged=function(){var t,i,e,r;for(r=["top","right","bottom","left"],t=0,i=r.length;i>t;t++)e=r[t],(null==this.options.margins||null==this.options.margins[e])&&(this.hasAxis(e)?this.margins[e]=n[e]:this.margins[e]=6);return this.g.transition().duration(750).attr("transform","translate("+this.margins.left+", "+this.margins.top+")"),this.g.selectAll(".axis").remove(),this._axesDrawn=!1,this.draw()},i.prototype.ticksChanged=function(){return this.draw()},i.prototype.tickFormatsChanged=function(){return this.draw()},i.prototype.domainChanged=function(){return this.draw()},i.prototype.rangeChanged=function(){return this.draw()},i}(Epoch.Chart.SVG);var extend=function(t,i){function n(){this.constructor=t}for(var e in i)hasProp.call(i,e)&&(t[e]=i[e]);return n.prototype=i.prototype,t.prototype=new n,t.__super__=i.prototype,t},hasProp={}.hasOwnProperty;Epoch.Chart.Area=function(t){function i(t){var n;this.options=null!=t?t:{},null==(n=this.options).type&&(n.type="area"),i.__super__.constructor.call(this,this.options),this.draw()}return extend(i,t),i.prototype.y=function(){var t,i,n,e,r,o,s,a,h;for(t=[],o=this.getVisibleLayers(),i=0,r=o.length;r>i;i++){e=o[i],s=e.values;for(n in s)hasProp.call(s,n)&&(h=s[n],null!=t[n]&&(t[n]+=h.y),null==t[n]&&(t[n]=h.y))}return d3.scale.linear().domain(null!=(a=this.options.range)?a:[0,d3.max(t)]).range([this.height-this.margins.top-this.margins.bottom,0])},i.prototype.draw=function(){var t,n,e,r,o,s,a,h;return o=[this.x(),this.y(),this.getVisibleLayers()],a=o[0],h=o[1],r=o[2],this.g.selectAll(".layer").remove(),0!==r.length?(t=d3.svg.area().x(function(t){return a(t.x)}).y0(function(t){return h(t.y0)}).y1(function(t){return h(t.y0+t.y)}),s=d3.layout.stack().values(function(t){return t.values}),n=s(r),e=this.g.selectAll(".layer").data(r,function(t){return t.category}),e.select(".area").attr("d",function(i){return t(i.values)}),e.enter().append("g").attr("class",function(t){return t.className}),e.append("path").attr("class","area").attr("d",function(i){return t(i.values)}),i.__super__.draw.call(this)):void 0},i}(Epoch.Chart.Plot);var extend=function(t,i){function n(){this.constructor=t}for(var e in i)hasProp.call(i,e)&&(t[e]=i[e]);return n.prototype=i.prototype,t.prototype=new n,t.__super__=i.prototype,t},hasProp={}.hasOwnProperty;Epoch.Chart.Bar=function(t){function i(t){this.options=null!=t?t:{},this._isHorizontal()?this.options=Epoch.Util.defaults(this.options,e):this.options=Epoch.Util.defaults(this.options,n),i.__super__.constructor.call(this,this.options),this.onAll(o),this.draw()}var n,e,r,o;return extend(i,t),n={type:"bar",style:"grouped",orientation:"vertical",padding:{bar:.08,group:.1},outerPadding:{bar:.08,group:.1}},r={tickFormats:{top:Epoch.Formats.si,bottom:Epoch.Formats.si,left:Epoch.Formats.regular,right:Epoch.Formats.regular}},e=Epoch.Util.defaults(r,n),o={"option:orientation":"orientationChanged","option:padding":"paddingChanged","option:outerPadding":"paddingChanged","option:padding:bar":"paddingChanged","option:padding:group":"paddingChanged","option:outerPadding:bar":"paddingChanged","option:outerPadding:group":"paddingChanged"},i.prototype._isVertical=function(){return"vertical"===this.options.orientation},i.prototype._isHorizontal=function(){return"horizontal"===this.options.orientation},i.prototype.x=function(){var t;return this._isVertical()?d3.scale.ordinal().domain(Epoch.Util.domain(this.getVisibleLayers())).rangeRoundBands([0,this.innerWidth()],this.options.padding.group,this.options.outerPadding.group):(t=this.extent(function(t){return t.y}),t[0]=Math.min(0,t[0]),d3.scale.linear().domain(t).range([0,this.width-this.margins.left-this.margins.right]))},i.prototype.x1=function(t){var i;return d3.scale.ordinal().domain(function(){var t,n,e,r;for(e=this.getVisibleLayers(),r=[],t=0,n=e.length;n>t;t++)i=e[t],r.push(i.category);return r}.call(this)).rangeRoundBands([0,t.rangeBand()],this.options.padding.bar,this.options.outerPadding.bar)},i.prototype.y=function(){var t;return this._isVertical()?(t=this.extent(function(t){return t.y}),t[0]=Math.min(0,t[0]),d3.scale.linear().domain(t).range([this.height-this.margins.top-this.margins.bottom,0])):d3.scale.ordinal().domain(Epoch.Util.domain(this.getVisibleLayers())).rangeRoundBands([0,this.innerHeight()],this.options.padding.group,this.options.outerPadding.group)},i.prototype.y1=function(t){var i;return d3.scale.ordinal().domain(function(){var t,n,e,r;for(e=this.getVisibleLayers(),r=[],t=0,n=e.length;n>t;t++)i=e[t],r.push(i.category);return r}.call(this)).rangeRoundBands([0,t.rangeBand()],this.options.padding.bar,this.options.outerPadding.bar)},i.prototype._remapData=function(){var t,i,n,e,r,o,s,a,h,p,l,u,c,g;for(h={},l=this.getVisibleLayers(),n=0,s=l.length;s>n;n++)for(o=l[n],t="bar "+o.className.replace(/\s*layer\s*/,""),u=o.values,r=0,a=u.length;a>r;r++)i=u[r],null==h[p=i.x]&&(h[p]=[]),h[i.x].push({label:o.category,y:i.y,className:t});c=[];for(e in h)hasProp.call(h,e)&&(g=h[e],c.push({group:e,values:g}));return c},i.prototype.draw=function(){return this._isVertical()?this._drawVertical():this._drawHorizontal(),i.__super__.draw.call(this)},i.prototype._drawVertical=function(){var t,i,n,e,r,o,s,a;return r=[this.x(),this.y()],o=r[0],a=r[1],s=this.x1(o),i=this.height-this.margins.top-this.margins.bottom,t=this._remapData(),n=this.g.selectAll(".layer").data(t,function(t){return t.group}),n.transition().duration(750).attr("transform",function(t){return"translate("+o(t.group)+", 0)"}),n.enter().append("g").attr("class","layer").attr("transform",function(t){return"translate("+o(t.group)+", 0)"}),e=n.selectAll("rect").data(function(t){return t.values}),e.attr("class",function(t){return t.className}),e.transition().duration(600).attr("x",function(t){return s(t.label)}).attr("y",function(t){return a(t.y)}).attr("width",s.rangeBand()).attr("height",function(t){return i-a(t.y)}),e.enter().append("rect").attr("class",function(t){return t.className}).attr("x",function(t){return s(t.label)}).attr("y",function(t){return a(t.y)}).attr("width",s.rangeBand()).attr("height",function(t){
+return i-a(t.y)}),e.exit().transition().duration(150).style("opacity","0").remove(),n.exit().transition().duration(750).style("opacity","0").remove()},i.prototype._drawHorizontal=function(){var t,i,n,e,r,o,s,a;return e=[this.x(),this.y()],o=e[0],s=e[1],a=this.y1(s),r=this.width-this.margins.left-this.margins.right,t=this._remapData(),i=this.g.selectAll(".layer").data(t,function(t){return t.group}),i.transition().duration(750).attr("transform",function(t){return"translate(0, "+s(t.group)+")"}),i.enter().append("g").attr("class","layer").attr("transform",function(t){return"translate(0, "+s(t.group)+")"}),n=i.selectAll("rect").data(function(t){return t.values}),n.attr("class",function(t){return t.className}),n.transition().duration(600).attr("x",function(t){return 0}).attr("y",function(t){return a(t.label)}).attr("height",a.rangeBand()).attr("width",function(t){return o(t.y)}),n.enter().append("rect").attr("class",function(t){return t.className}).attr("x",function(t){return 0}).attr("y",function(t){return a(t.label)}).attr("height",a.rangeBand()).attr("width",function(t){return o(t.y)}),n.exit().transition().duration(150).style("opacity","0").remove(),i.exit().transition().duration(750).style("opacity","0").remove()},i.prototype._getTickValues=function(t,i){var n,e,r,o;return null==i&&(i="x"),null==this.data[0]?[]:(o=this.data[0].values.length,e=0|Math.ceil(o/t),r=function(){var t,i,r,s;for(s=[],n=t=0,i=o,r=e;r>0?i>t:t>i;n=t+=r)s.push(this.data[0].values[n].x);return s}.call(this))},i.prototype.bottomAxis=function(){var t;return t=d3.svg.axis().scale(this.x()).orient("bottom").ticks(this.options.ticks.bottom).tickFormat(this.options.tickFormats.bottom),this._isVertical()&&null!=this.options.ticks.bottom&&t.tickValues(this._getTickValues(this.options.ticks.bottom)),t},i.prototype.topAxis=function(){var t;return t=d3.svg.axis().scale(this.x()).orient("top").ticks(this.options.ticks.top).tickFormat(this.options.tickFormats.top),this._isVertical()&&null!=this.options.ticks.top&&t.tickValues(this._getTickValues(this.options.ticks.top)),t},i.prototype.leftAxis=function(){var t;return t=d3.svg.axis().scale(this.y()).orient("left").ticks(this.options.ticks.left).tickFormat(this.options.tickFormats.left),this._isHorizontal()&&null!=this.options.ticks.left&&t.tickValues(this._getTickValues(this.options.ticks.left)),t},i.prototype.rightAxis=function(){var t;return t=d3.svg.axis().scale(this.y()).orient("right").ticks(this.options.ticks.right).tickFormat(this.options.tickFormats.right),this._isHorizontal()&&null!=this.options.ticks.right&&t.tickValues(this._getTickValues(this.options.ticks.right)),t},i.prototype.orientationChanged=function(){var t,i,n,e;return e=this.options.tickFormats.top,t=this.options.tickFormats.bottom,i=this.options.tickFormats.left,n=this.options.tickFormats.right,this.options.tickFormats.left=e,this.options.tickFormats.right=t,this.options.tickFormats.top=i,this.options.tickFormats.bottom=n,this.draw()},i.prototype.paddingChanged=function(){return this.draw()},i}(Epoch.Chart.Plot);var extend=function(t,i){function n(){this.constructor=t}for(var e in i)hasProp.call(i,e)&&(t[e]=i[e]);return n.prototype=i.prototype,t.prototype=new n,t.__super__=i.prototype,t},hasProp={}.hasOwnProperty;Epoch.Chart.Histogram=function(t){function i(t){this.options=null!=t?t:{},i.__super__.constructor.call(this,this.options=Epoch.Util.defaults(this.options,n)),this.onAll(e),this.draw()}var n,e;return extend(i,t),n={type:"histogram",domain:[0,100],bucketRange:[0,100],buckets:10,cutOutliers:!1},e={"option:bucketRange":"bucketRangeChanged","option:buckets":"bucketsChanged","option:cutOutliers":"cutOutliersChanged"},i.prototype._prepareData=function(t){var i,n,e,r,o,s,a,h,p,l,u,c,g,d,f;for(i=(this.options.bucketRange[1]-this.options.bucketRange[0])/this.options.buckets,c=[],o=0,p=t.length;p>o;o++){for(h=t[o],n=function(){var t,i,n;for(n=[],e=t=0,i=this.options.buckets;i>=0?i>t:t>i;e=i>=0?++t:--t)n.push(0);return n}.call(this),d=h.values,a=0,l=d.length;l>a;a++)u=d[a],r=parseInt((u.x-this.options.bucketRange[0])/i),this.options.cutOutliers&&(0>r||r>=this.options.buckets)||(0>r?r=0:r>=this.options.buckets&&(r=this.options.buckets-1),n[r]+=parseInt(u.y));g={values:n.map(function(t,n){return{x:parseInt(n)*i,y:t}})};for(s in h)hasProp.call(h,s)&&(f=h[s],"values"!==s&&(g[s]=f));c.push(g)}return c},i.prototype.resetData=function(){return this.setData(this.rawData),this.draw()},i.prototype.bucketRangeChanged=function(){return this.resetData()},i.prototype.bucketsChanged=function(){return this.resetData()},i.prototype.cutOutliersChanged=function(){return this.resetData()},i}(Epoch.Chart.Bar);var extend=function(t,i){function n(){this.constructor=t}for(var e in i)hasProp.call(i,e)&&(t[e]=i[e]);return n.prototype=i.prototype,t.prototype=new n,t.__super__=i.prototype,t},hasProp={}.hasOwnProperty;Epoch.Chart.Line=function(t){function i(t){var n;this.options=null!=t?t:{},null==(n=this.options).type&&(n.type="line"),i.__super__.constructor.call(this,this.options),this.draw()}return extend(i,t),i.prototype.line=function(t){var i,n,e;return i=[this.x(),this.y(t.range)],n=i[0],e=i[1],d3.svg.line().x(function(t){return n(t.x)}).y(function(t){return e(t.y)})},i.prototype.draw=function(){var t,n,e,r,o;return e=[this.x(),this.y(),this.getVisibleLayers()],r=e[0],o=e[1],n=e[2],0===n.length?this.g.selectAll(".layer").remove():(t=this.g.selectAll(".layer").data(n,function(t){return t.category}),t.select(".line").transition().duration(500).attr("d",function(t){return function(i){return t.line(i)(i.values)}}(this)),t.enter().append("g").attr("class",function(t){return t.className}).append("path").attr("class","line").attr("d",function(t){return function(i){return t.line(i)(i.values)}}(this)),t.exit().transition().duration(750).style("opacity","0").remove(),i.__super__.draw.call(this))},i}(Epoch.Chart.Plot);var extend=function(t,i){function n(){this.constructor=t}for(var e in i)hasProp.call(i,e)&&(t[e]=i[e]);return n.prototype=i.prototype,t.prototype=new n,t.__super__=i.prototype,t},hasProp={}.hasOwnProperty;Epoch.Chart.Pie=function(t){function i(t){this.options=null!=t?t:{},i.__super__.constructor.call(this,this.options=Epoch.Util.defaults(this.options,n)),this.pie=d3.layout.pie().sort(null).value(function(t){return t.value}),this.arc=d3.svg.arc().outerRadius(function(t){return function(){return Math.max(t.width,t.height)/2-t.options.margin}}(this)).innerRadius(function(t){return function(){return t.options.inner}}(this)),this.g=this.svg.append("g").attr("transform","translate("+this.width/2+", "+this.height/2+")"),this.on("option:margin","marginChanged"),this.on("option:inner","innerChanged"),this.draw()}var n;return extend(i,t),n={type:"pie",margin:10,inner:0},i.prototype.draw=function(){var t,n,e;return this.g.selectAll(".arc").remove(),t=this.g.selectAll(".arc").data(this.pie(this.getVisibleLayers()),function(t){return t.data.category}),t.enter().append("g").attr("class",function(t){return"arc pie "+t.data.className}),t.select("path").attr("d",this.arc),t.select("text").attr("transform",function(t){return function(i){return"translate("+t.arc.centroid(i)+")"}}(this)).text(function(t){return t.data.label||t.data.category}),n=t.append("path").attr("d",this.arc).each(function(t){return this._current=t}),e=t.append("text").attr("transform",function(t){return function(i){return"translate("+t.arc.centroid(i)+")"}}(this)).attr("dy",".35em").style("text-anchor","middle").text(function(t){return t.data.label||t.data.category}),i.__super__.draw.call(this)},i.prototype.marginChanged=function(){return this.draw()},i.prototype.innerChanged=function(){return this.draw()},i}(Epoch.Chart.SVG);var extend=function(t,i){function n(){this.constructor=t}for(var e in i)hasProp.call(i,e)&&(t[e]=i[e]);return n.prototype=i.prototype,t.prototype=new n,t.__super__=i.prototype,t},hasProp={}.hasOwnProperty;Epoch.Chart.Scatter=function(t){function i(t){this.options=null!=t?t:{},i.__super__.constructor.call(this,this.options=Epoch.Util.defaults(this.options,n)),this.on("option:radius","radiusChanged"),this.draw()}var n;return extend(i,t),n={type:"scatter",radius:3.5,axes:["top","bottom","left","right"]},i.prototype.draw=function(){var t,n,e,r,o,s,a;return o=[this.x(),this.y(),this.getVisibleLayers()],s=o[0],a=o[1],e=o[2],r=this.options.radius,0===e.length?this.g.selectAll(".layer").remove():(n=this.g.selectAll(".layer").data(e,function(t){return t.category}),n.enter().append("g").attr("class",function(t){return t.className}),t=n.selectAll(".dot").data(function(t){return t.values}),t.transition().duration(500).attr("r",function(t){var i;return null!=(i=t.r)?i:r}).attr("cx",function(t){return s(t.x)}).attr("cy",function(t){return a(t.y)}),t.enter().append("circle").attr("class","dot").attr("r",function(t){var i;return null!=(i=t.r)?i:r}).attr("cx",function(t){return s(t.x)}).attr("cy",function(t){return a(t.y)}),t.exit().transition().duration(750).style("opacity",0).remove(),n.exit().transition().duration(750).style("opacity",0).remove(),i.__super__.draw.call(this))},i.prototype.radiusChanged=function(){return this.draw()},i}(Epoch.Chart.Plot);var extend=function(t,i){function n(){this.constructor=t}for(var e in i)hasProp.call(i,e)&&(t[e]=i[e]);return n.prototype=i.prototype,t.prototype=new n,t.__super__=i.prototype,t},hasProp={}.hasOwnProperty;Epoch.Time.Plot=function(t){function i(t){var o,s,a,h,p;for(this.options=t,o=Epoch.Util.copy(this.options.margins)||{},i.__super__.constructor.call(this,this.options=Epoch.Util.defaults(this.options,e)),this.options.model&&this.options.model.on("data:push",function(t){return function(){return t.pushFromModel()}}(this)),this._queue=[],this.margins={},p=["top","right","bottom","left"],s=0,a=p.length;a>s;s++)h=p[s],this.margins[h]=null!=this.options.margins&&null!=this.options.margins[h]?this.options.margins[h]:this.hasAxis(h)?n[h]:6;this.svg=this.el.insert("svg",":first-child").attr("width",this.width).attr("height",this.height).style("z-index","1000"),"absolute"!==this.el.style("position")&&"relative"!==this.el.style("position")&&this.el.style("position","relative"),this.canvas.style({position:"absolute","z-index":"999"}),this._sizeCanvas(),this.animation={interval:null,active:!1,delta:function(t){return function(){return-(t.w()/t.options.fps)}}(this),tickDelta:function(t){return function(){return-(t.w()/t.pixelRatio/t.options.fps)}}(this),frame:0,duration:this.options.fps},this._buildAxes(),this.animationCallback=function(t){return function(){return t._animate()}}(this),this.onAll(r)}var n,e,r;return extend(i,t),e={range:null,fps:24,historySize:120,windowSize:40,queueSize:10,axes:["bottom"],ticks:{time:15,left:5,right:5},tickFormats:{top:Epoch.Formats.seconds,bottom:Epoch.Formats.seconds,left:Epoch.Formats.si,right:Epoch.Formats.si}},n={top:25,right:50,bottom:25,left:50},r={"option:margins":"marginsChanged","option:margins.top":"marginsChanged","option:margins.right":"marginsChanged","option:margins.bottom":"marginsChanged","option:margins.left":"marginsChanged","option:axes":"axesChanged","option:ticks":"ticksChanged","option:ticks.top":"ticksChanged","option:ticks.right":"ticksChanged","option:ticks.bottom":"ticksChanged","option:ticks.left":"ticksChanged","option:tickFormats":"tickFormatsChanged","option:tickFormats.top":"tickFormatsChanged","option:tickFormats.right":"tickFormatsChanged","option:tickFormats.bottom":"tickFormatsChanged","option:tickFormats.left":"tickFormatsChanged"},i.prototype._sizeCanvas=function(){return this.canvas.attr({width:this.innerWidth(),height:this.innerHeight()}),this.canvas.style({width:this.innerWidth()/this.pixelRatio+"px",height:this.innerHeight()/this.pixelRatio+"px",top:this.margins.top+"px",left:this.margins.left+"px"})},i.prototype._buildAxes=function(){return this.svg.selectAll(".axis").remove(),this._prepareTimeAxes(),this._prepareRangeAxes()},i.prototype._annotateLayers=function(t){var i,n,e,r,o,s;e=[];for(r in t)hasProp.call(t,r)&&(o=t[r],n=Epoch.Util.copy(o),s=Math.max(0,o.values.length-this.options.historySize),n.values=o.values.slice(s),i=["layer"],i.push("category"+((0|r)+1)),null!=o.label&&i.push(Epoch.Util.dasherize(o.label)),n.className=i.join(" "),n.visible=!0,e.push(n));return e},i.prototype._offsetX=function(){return 0},i.prototype._prepareTimeAxes=function(){var t;return this.hasAxis("bottom")&&(t=this.bottomAxis=this.svg.append("g").attr("class","x axis bottom canvas").attr("transform","translate("+(this.margins.left-1)+", "+(this.innerHeight()/this.pixelRatio+this.margins.top)+")"),t.append("path").attr("class","domain").attr("d","M0,0H"+(this.innerWidth()/this.pixelRatio+1))),this.hasAxis("top")&&(t=this.topAxis=this.svg.append("g").attr("class","x axis top canvas").attr("transform","translate("+(this.margins.left-1)+", "+this.margins.top+")"),t.append("path").attr("class","domain").attr("d","M0,0H"+(this.innerWidth()/this.pixelRatio+1))),this._resetInitialTimeTicks()},i.prototype._resetInitialTimeTicks=function(){var t,i,n,e,r,o,s,a,h;for(h=this.options.ticks.time,this._ticks=[],this._tickTimer=h,null!=this.bottomAxis&&this.bottomAxis.selectAll(".tick").remove(),null!=this.topAxis&&this.topAxis.selectAll(".tick").remove(),o=this.data,a=[],n=0,r=o.length;r>n;n++)if(e=o[n],Epoch.isNonEmptyArray(e.values)){for(s=[this.options.windowSize-1,e.values.length-1],t=s[0],i=s[1];t>=0&&i>=0;)this._pushTick(t,e.values[i].time,!1,!0),t-=h,i-=h;break}return a},i.prototype._prepareRangeAxes=function(){return this.hasAxis("left")&&this.svg.append("g").attr("class","y axis left").attr("transform","translate("+(this.margins.left-1)+", "+this.margins.top+")").call(this.leftAxis()),this.hasAxis("right")?this.svg.append("g").attr("class","y axis right").attr("transform","translate("+(this.width-this.margins.right)+", "+this.margins.top+")").call(this.rightAxis()):void 0},i.prototype.leftAxis=function(){var t,i;return i=this.options.ticks.left,t=d3.svg.axis().scale(this.ySvgLeft()).orient("left").tickFormat(this.options.tickFormats.left),2===i?t.tickValues(this.extent(function(t){return t.y})):t.ticks(i)},i.prototype.rightAxis=function(){var t,i,n;return i=this.extent(function(t){return t.y}),n=this.options.ticks.right,t=d3.svg.axis().scale(this.ySvgRight()).orient("right").tickFormat(this.options.tickFormats.right),2===n?t.tickValues(this.extent(function(t){return t.y})):t.ticks(n)},i.prototype.hasAxis=function(t){return this.options.axes.indexOf(t)>-1},i.prototype.innerWidth=function(){return(this.width-(this.margins.left+this.margins.right))*this.pixelRatio},i.prototype.innerHeight=function(){return(this.height-(this.margins.top+this.margins.bottom))*this.pixelRatio},i.prototype._prepareEntry=function(t){return t},i.prototype._prepareLayers=function(t){return t},i.prototype._startTransition=function(){return this.animation.active!==!0&&0!==this._queue.length?(this.trigger("transition:start"),this._shift(),this.animation.active=!0,this.animation.interval=setInterval(this.animationCallback,1e3/this.options.fps)):void 0},i.prototype._stopTransition=function(){var t,i,n,e,r,o,s;if(this.inTransition()){for(o=this.data,i=0,r=o.length;r>i;i++)e=o[i],e.values.length>this.options.windowSize+1&&e.values.shift();return s=[this._ticks[0],this._ticks[this._ticks.length-1]],t=s[0],n=s[1],null!=n&&n.enter&&(n.enter=!1,n.opacity=1),null!=t&&t.exit&&this._shiftTick(),this.animation.frame=0,this.trigger("transition:end"),this._queue.length>0?this._shift():(this.animation.active=!1,clearInterval(this.animation.interval))}},i.prototype.inTransition=function(){return this.animation.active},i.prototype.push=function(t){return t=this._prepareLayers(t),this._queue.length>this.options.queueSize&&this._queue.splice(this.options.queueSize,this._queue.length-this.options.queueSize),this._queue.length===this.options.queueSize?!1:(this._queue.push(t.map(function(t){return function(i){return t._prepareEntry(i)}}(this))),this.trigger("push"),this.inTransition()?void 0:this._startTransition())},i.prototype.pushFromModel=function(){return this.push(this.options.model.getNext(this.options.type,this.options.dataFormat))},i.prototype._shift=function(){var t,i,n,e;this.trigger("before:shift"),t=this._queue.shift(),e=this.data;for(i in e)hasProp.call(e,i)&&(n=e[i],n.values.push(t[i]));return this._updateTicks(t[0].time),this._transitionRangeAxes(),this.trigger("after:shift")},i.prototype._transitionRangeAxes=function(){return this.hasAxis("left")&&this.svg.selectAll(".y.axis.left").transition().duration(500).ease("linear").call(this.leftAxis()),this.hasAxis("right")?this.svg.selectAll(".y.axis.right").transition().duration(500).ease("linear").call(this.rightAxis()):void 0},i.prototype._animate=function(){return this.inTransition()?(++this.animation.frame===this.animation.duration&&this._stopTransition(),this.draw(this.animation.frame*this.animation.delta()),this._updateTimeAxes()):void 0},i.prototype.y=function(t){return d3.scale.linear().domain(this._getScaleDomain(t)).range([this.innerHeight(),0])},i.prototype.ySvg=function(t){return d3.scale.linear().domain(this._getScaleDomain(t)).range([this.innerHeight()/this.pixelRatio,0])},i.prototype.ySvgLeft=function(){return null!=this.options.range?this.ySvg(this.options.range.left):this.ySvg()},i.prototype.ySvgRight=function(){return null!=this.options.range?this.ySvg(this.options.range.right):this.ySvg()},i.prototype.w=function(){return this.innerWidth()/this.options.windowSize},i.prototype._updateTicks=function(t){return(this.hasAxis("top")||this.hasAxis("bottom"))&&(++this._tickTimer%this.options.ticks.time||this._pushTick(this.options.windowSize,t,!0),this._ticks.length>0)?this._ticks[0].x-this.w()/this.pixelRatio>=0?void 0:this._ticks[0].exit=!0:void 0},i.prototype._pushTick=function(t,i,n,e){var r,o;return null==n&&(n=!1),null==e&&(e=!1),this.hasAxis("top")||this.hasAxis("bottom")?(o={time:i,x:t*(this.w()/this.pixelRatio)+this._offsetX(),opacity:n?0:1,enter:n?!0:!1,exit:!1},this.hasAxis("bottom")&&(r=this.bottomAxis.append("g").attr("class","tick major").attr("transform","translate("+(o.x+1)+",0)").style("opacity",o.opacity),r.append("line").attr("y2",6),r.append("text").attr("text-anchor","middle").attr("dy",19).text(this.options.tickFormats.bottom(o.time)),o.bottomEl=r),this.hasAxis("top")&&(r=this.topAxis.append("g").attr("class","tick major").attr("transform","translate("+(o.x+1)+",0)").style("opacity",o.opacity),r.append("line").attr("y2",-6),r.append("text").attr("text-anchor","middle").attr("dy",-10).text(this.options.tickFormats.top(o.time)),o.topEl=r),e?this._ticks.unshift(o):this._ticks.push(o),o):void 0},i.prototype._shiftTick=function(){var t;if(this._ticks.length>0)return t=this._ticks.shift(),null!=t.topEl&&t.topEl.remove(),null!=t.bottomEl?t.bottomEl.remove():void 0},i.prototype._updateTimeAxes=function(){var t,i,n,e,r,o,s,a;if(this.hasAxis("top")||this.hasAxis("bottom")){for(r=[this.animation.tickDelta(),1/this.options.fps],i=r[0],t=r[1],o=this._ticks,s=[],n=0,e=o.length;e>n;n++)a=o[n],a.x+=i,this.hasAxis("bottom")&&a.bottomEl.attr("transform","translate("+(a.x+1)+",0)"),this.hasAxis("top")&&a.topEl.attr("transform","translate("+(a.x+1)+",0)"),a.enter?a.opacity+=t:a.exit&&(a.opacity-=t),a.enter||a.exit?(this.hasAxis("bottom")&&a.bottomEl.style("opacity",a.opacity),this.hasAxis("top")?s.push(a.topEl.style("opacity",a.opacity)):s.push(void 0)):s.push(void 0);return s}},i.prototype.draw=function(t){return null==t&&(t=0),i.__super__.draw.call(this)},i.prototype.dimensionsChanged=function(){return i.__super__.dimensionsChanged.call(this),this.svg.attr("width",this.width).attr("height",this.height),this._sizeCanvas(),this._buildAxes(),this.draw(this.animation.frame*this.animation.delta())},i.prototype.axesChanged=function(){var t,i,e,r;for(r=["top","right","bottom","left"],t=0,i=r.length;i>t;t++)e=r[t],(null==this.options.margins||null==this.options.margins[e])&&(this.hasAxis(e)?this.margins[e]=n[e]:this.margins[e]=6);return this._sizeCanvas(),this._buildAxes(),this.draw(this.animation.frame*this.animation.delta())},i.prototype.ticksChanged=function(){return this._resetInitialTimeTicks(),this._transitionRangeAxes(),this.draw(this.animation.frame*this.animation.delta())},i.prototype.tickFormatsChanged=function(){return this._resetInitialTimeTicks(),this._transitionRangeAxes(),this.draw(this.animation.frame*this.animation.delta())},i.prototype.marginsChanged=function(){var t,i,n;if(null!=this.options.margins){i=this.options.margins;for(t in i)hasProp.call(i,t)&&(n=i[t],null==n?this.margins[t]=6:this.margins[t]=n);return this._sizeCanvas(),this.draw(this.animation.frame*this.animation.delta())}},i.prototype.layerChanged=function(){return this._transitionRangeAxes(),i.__super__.layerChanged.call(this)},i}(Epoch.Chart.Canvas),Epoch.Time.Stack=function(t){function i(){return i.__super__.constructor.apply(this,arguments)}return extend(i,t),i.prototype._stackLayers=function(){var t,i,n,e,r,o,s;if((e=this.getVisibleLayers()).length>0){for(o=[],t=i=0,r=e[0].values.length;r>=0?r>i:i>r;t=r>=0?++i:--i)s=0,o.push(function(){var i,r,o;for(o=[],r=0,i=e.length;i>r;r++)n=e[r],n.values[t].y0=s,o.push(s+=n.values[t].y);return o}());return o}},i.prototype._prepareLayers=function(t){var i,n,e;e=0;for(n in t)hasProp.call(t,n)&&(i=t[n],this.data[n].visible&&(i.y0=e,e+=i.y));return t},i.prototype.setData=function(t){return i.__super__.setData.call(this,t),this._stackLayers()},i.prototype.extent=function(){var t,i,n,e,r,o,s,a,h,p;if(s=[0,this.getVisibleLayers()],o=s[0],e=s[1],!e.length)return[0,0];for(t=n=0,a=e[0].values.length;a>=0?a>n:n>a;t=a>=0?++n:--n){for(p=0,i=r=0,h=e.length;h>=0?h>r:r>h;i=h>=0?++r:--r)p+=e[i].values[t].y;p>o&&(o=p)}return[0,o]},i.prototype.layerChanged=function(){var t,n,e,r;for(this._stackLayers(),r=this._queue,t=0,e=r.length;e>t;t++)n=r[t],this._prepareLayers(n);return i.__super__.layerChanged.call(this)},i}(Epoch.Time.Plot);var extend=function(t,i){function n(){this.constructor=t}for(var e in i)hasProp.call(i,e)&&(t[e]=i[e]);return n.prototype=i.prototype,t.prototype=new n,t.__super__=i.prototype,t},hasProp={}.hasOwnProperty;Epoch.Time.Area=function(t){function i(t){var n;this.options=null!=t?t:{},null==(n=this.options).type&&(n.type="time.area"),i.__super__.constructor.call(this,this.options),this.draw()}return extend(i,t),i.prototype.setStyles=function(t){var i;return i=null!=t&&null!=t.className?this.getStyles("g."+t.className.replace(/\s/g,".")+" path.area"):this.getStyles("g path.area"),this.ctx.fillStyle=i.fill,null!=i.stroke&&(this.ctx.strokeStyle=i.stroke),null!=i["stroke-width"]?this.ctx.lineWidth=i["stroke-width"].replace("px",""):void 0},i.prototype._drawAreas=function(t){var i,n,e,r,o,s,a,h,p,l,u,c,g,d,f,y,m;for(null==t&&(t=0),u=[this.y(),this.w(),this.getVisibleLayers()],m=u[0],y=u[1],l=u[2],d=[],o=h=c=l.length-1;0>=c?0>=h:h>=0;o=0>=c?++h:--h)if(p=l[o]){for(this.setStyles(p),this.ctx.beginPath(),g=[this.options.windowSize,p.values.length,this.inTransition()],s=g[0],a=g[1],f=g[2],r=null;--s>=-2&&--a>=0;)e=p.values[a],i=[(s+1)*y+t,m(e.y+e.y0)],f&&(i[0]+=y),o===this.options.windowSize-1?this.ctx.moveTo.apply(this.ctx,i):this.ctx.lineTo.apply(this.ctx,i);n=f?(s+3)*y+t:(s+2)*y+t,this.ctx.lineTo(n,this.innerHeight()),this.ctx.lineTo(this.width*this.pixelRatio+y+t,this.innerHeight()),this.ctx.closePath(),d.push(this.ctx.fill())}return d},i.prototype._drawStrokes=function(t){var i,n,e,r,o,s,a,h,p,l,u,c,g,d,f;for(null==t&&(t=0),p=[this.y(),this.w(),this.getVisibleLayers()],f=p[0],d=p[1],h=p[2],c=[],r=s=l=h.length-1;0>=l?0>=s:s>=0;r=0>=l?++s:--s)if(a=h[r]){for(this.setStyles(a),this.ctx.beginPath(),u=[this.options.windowSize,a.values.length,this.inTransition()],r=u[0],o=u[1],g=u[2],e=null;--r>=-2&&--o>=0;)n=a.values[o],i=[(r+1)*d+t,f(n.y+n.y0)],g&&(i[0]+=d),r===this.options.windowSize-1?this.ctx.moveTo.apply(this.ctx,i):this.ctx.lineTo.apply(this.ctx,i);c.push(this.ctx.stroke())}return c},i.prototype.draw=function(t){return null==t&&(t=0),this.clear(),this._drawAreas(t),this._drawStrokes(t),i.__super__.draw.call(this)},i}(Epoch.Time.Stack);var extend=function(t,i){function n(){this.constructor=t}for(var e in i)hasProp.call(i,e)&&(t[e]=i[e]);return n.prototype=i.prototype,t.prototype=new n,t.__super__=i.prototype,t},hasProp={}.hasOwnProperty;Epoch.Time.Bar=function(t){function i(t){var n;this.options=null!=t?t:{},null==(n=this.options).type&&(n.type="time.bar"),i.__super__.constructor.call(this,this.options),this.draw()}return extend(i,t),i.prototype._offsetX=function(){return.5*this.w()/this.pixelRatio},i.prototype.setStyles=function(t){var i;return i=this.getStyles("rect.bar."+t.replace(/\s/g,".")),this.ctx.fillStyle=i.fill,null==i.stroke||"none"===i.stroke?this.ctx.strokeStyle="transparent":this.ctx.strokeStyle=i.stroke,null!=i["stroke-width"]?this.ctx.lineWidth=i["stroke-width"].replace("px",""):void 0},i.prototype.draw=function(t){var n,e,r,o,s,a,h,p,l,u,c,g,d,f,y,m,v,x;for(null==t&&(t=0),this.clear(),g=[this.y(),this.w()],x=g[0],v=g[1],d=this.getVisibleLayers(),p=0,c=d.length;c>p;p++)if(u=d[p],Epoch.isNonEmptyArray(u.values))for(this.setStyles(u.className),f=[this.options.windowSize,u.values.length,this.inTransition()],a=f[0],l=f[1],m=f[2],h=m?-1:0;--a>=h&&--l>=0;)e=u.values[l],y=[a*v+t,e.y,e.y0],r=y[0],o=y[1],s=y[2],m&&(r+=v),n=[r+1,x(o+s),v-2,this.innerHeight()-x(o)+.5*this.pixelRatio],this.ctx.fillRect.apply(this.ctx,n),this.ctx.strokeRect.apply(this.ctx,n);return i.__super__.draw.call(this)},i}(Epoch.Time.Stack);var extend=function(t,i){function n(){this.constructor=t}for(var e in i)hasProp.call(i,e)&&(t[e]=i[e]);return n.prototype=i.prototype,t.prototype=new n,t.__super__=i.prototype,t},hasProp={}.hasOwnProperty;Epoch.Time.Gauge=function(t){function i(t){this.options=null!=t?t:{},i.__super__.constructor.call(this,this.options=Epoch.Util.defaults(this.options,n)),this.value=this.options.value||0,this.options.model&&this.options.model.on("data:push",function(t){return function(){return t.pushFromModel()}}(this)),"absolute"!==this.el.style("position")&&"relative"!==this.el.style("position")&&this.el.style("position","relative"),this.svg=this.el.insert("svg",":first-child").attr("width",this.width).attr("height",this.height).attr("class","gauge-labels"),this.svg.style({position:"absolute","z-index":"1"}),this.svg.append("g").attr("transform","translate("+this.textX()+", "+this.textY()+")").append("text").attr("class","value").text(this.options.format(this.value)),this.animation={interval:null,active:!1,delta:0,target:0},this._animate=function(t){return function(){return Math.abs(t.animation.target-t.value)<Math.abs(t.animation.delta)?(t.value=t.animation.target,clearInterval(t.animation.interval),t.animation.active=!1):t.value+=t.animation.delta,t.svg.select("text.value").text(t.options.format(t.value)),t.draw()}}(this),this.onAll(e),this.draw()}var n,e;return extend(i,t),n={type:"time.gauge",domain:[0,1],ticks:10,tickSize:5,tickOffset:5,fps:34,format:Epoch.Formats.percent},e={"option:domain":"domainChanged","option:ticks":"ticksChanged","option:tickSize":"tickSizeChanged","option:tickOffset":"tickOffsetChanged","option:format":"formatChanged"},i.prototype.update=function(t){return this.animation.target=t,this.animation.delta=(t-this.value)/this.options.fps,this.animation.active?void 0:(this.animation.interval=setInterval(this._animate,1e3/this.options.fps),this.animation.active=!0)},i.prototype.push=function(t){return this.update(t)},i.prototype.pushFromModel=function(){var t;return t=this.options.model.getNext(this.options.type,this.options.dataFormat),this.update(t)},i.prototype.radius=function(){return this.getHeight()/1.58},i.prototype.centerX=function(){return this.getWidth()/2},i.prototype.centerY=function(){return.68*this.getHeight()},i.prototype.textX=function(){return this.width/2},i.prototype.textY=function(){return.48*this.height},i.prototype.getAngle=function(t){var i,n,e;return e=this.options.domain,i=e[0],n=e[1],(t-i)/(n-i)*(Math.PI+2*Math.PI/8)-Math.PI/2-Math.PI/8},i.prototype.setStyles=function(t){var i;return i=this.getStyles(t),this.ctx.fillStyle=i.fill,this.ctx.strokeStyle=i.stroke,null!=i["stroke-width"]?this.ctx.lineWidth=i["stroke-width"].replace("px",""):void 0},i.prototype.draw=function(){var t,n,e,r,o,s,a,h,p,l,u,c,g,d,f,y,m,v,x;for(h=[this.centerX(),this.centerY(),this.radius()],e=h[0],r=h[1],a=h[2],p=[this.options.tickOffset,this.options.tickSize],d=p[0],f=p[1],this.clear(),g=d3.scale.linear().domain([0,this.options.ticks]).range([-1.125*Math.PI,Math.PI/8]),this.setStyles(".epoch .gauge .tick"),this.ctx.beginPath(),o=s=0,l=this.options.ticks;l>=0?l>=s:s>=l;o=l>=0?++s:--s)t=g(o),u=[Math.cos(t),Math.sin(t)],n=u[0],c=u[1],y=n*(a-d)+e,v=c*(a-d)+r,m=n*(a-d-f)+e,x=c*(a-d-f)+r,this.ctx.moveTo(y,v),this.ctx.lineTo(m,x);return this.ctx.stroke(),this.setStyles(".epoch .gauge .arc.outer"),this.ctx.beginPath(),this.ctx.arc(e,r,a,-1.125*Math.PI,1/8*Math.PI,!1),this.ctx.stroke(),this.setStyles(".epoch .gauge .arc.inner"),this.ctx.beginPath(),this.ctx.arc(e,r,a-10,-1.125*Math.PI,1/8*Math.PI,!1),this.ctx.stroke(),this.drawNeedle(),i.__super__.draw.call(this)},i.prototype.drawNeedle=function(){var t,i,n,e,r;return r=[this.centerX(),this.centerY(),this.radius()],t=r[0],i=r[1],n=r[2],e=this.value/this.options.domain[1],this.setStyles(".epoch .gauge .needle"),this.ctx.beginPath(),this.ctx.save(),this.ctx.translate(t,i),this.ctx.rotate(this.getAngle(this.value)),this.ctx.moveTo(4*this.pixelRatio,0),this.ctx.lineTo(-4*this.pixelRatio,0),this.ctx.lineTo(-1*this.pixelRatio,19-n),this.ctx.lineTo(1,19-n),this.ctx.fill(),this.setStyles(".epoch .gauge .needle-base"),this.ctx.beginPath(),this.ctx.arc(0,0,this.getWidth()/25,0,2*Math.PI),this.ctx.fill(),this.ctx.restore()},i.prototype.domainChanged=function(){return this.draw()},i.prototype.ticksChanged=function(){return this.draw()},i.prototype.tickSizeChanged=function(){return this.draw()},i.prototype.tickOffsetChanged=function(){return this.draw()},i.prototype.formatChanged=function(){return this.svg.select("text.value").text(this.options.format(this.value))},i}(Epoch.Chart.Canvas);var extend=function(t,i){function n(){this.constructor=t}for(var e in i)hasProp.call(i,e)&&(t[e]=i[e]);return n.prototype=i.prototype,t.prototype=new n,t.__super__=i.prototype,t},hasProp={}.hasOwnProperty;Epoch.Time.Heatmap=function(t){function i(t){this.options=null!=t?t:{},i.__super__.constructor.call(this,this.options=Epoch.Util.defaults(this.options,e)),this._setOpacityFunction(),this._setupPaintCanvas(),this.onAll(r),this.draw()}var n,e,r;return extend(i,t),e={type:"time.heatmap",buckets:10,bucketRange:[0,100],opacity:"linear",bucketPadding:2,paintZeroValues:!1,cutOutliers:!1},n={root:function(t,i){return Math.pow(t/i,.5)},linear:function(t,i){return t/i},quadratic:function(t,i){return Math.pow(t/i,2)},cubic:function(t,i){return Math.pow(t/i,3)},quartic:function(t,i){return Math.pow(t/i,4)},quintic:function(t,i){return Math.pow(t/i,5)}},r={"option:buckets":"bucketsChanged","option:bucketRange":"bucketRangeChanged","option:opacity":"opacityChanged","option:bucketPadding":"bucketPaddingChanged","option:paintZeroValues":"paintZeroValuesChanged","option:cutOutliers":"cutOutliersChanged"},i.prototype._setOpacityFunction=function(){return Epoch.isString(this.options.opacity)?(this._opacityFn=n[this.options.opacity],null==this._opacityFn?Epoch.exception("Unknown coloring function provided '"+this.options.opacity+"'"):void 0):Epoch.isFunction(this.options.opacity)?this._opacityFn=this.options.opacity:Epoch.exception("Unknown type for provided coloring function.")},i.prototype.setData=function(t){var n,e,r,o,s;for(i.__super__.setData.call(this,t),o=this.data,s=[],n=0,r=o.length;r>n;n++)e=o[n],s.push(e.values=e.values.map(function(t){return function(i){return t._prepareEntry(i)}}(this)));return s},i.prototype._getBuckets=function(t){var i,n,e,r,o,s,a,h,p;s={time:t.time,max:0,buckets:function(){var t,i,n;for(n=[],e=t=0,i=this.options.buckets;i>=0?i>t:t>i;e=i>=0?++t:--t)n.push(0);return n}.call(this)},i=(this.options.bucketRange[1]-this.options.bucketRange[0])/this.options.buckets,
+a=t.histogram;for(p in a)hasProp.call(a,p)&&(n=a[p],r=parseInt((p-this.options.bucketRange[0])/i),this.options.cutOutliers&&(0>r||r>=this.options.buckets)||(0>r?r=0:r>=this.options.buckets&&(r=this.options.buckets-1),s.buckets[r]+=parseInt(n)));for(e=o=0,h=s.buckets.length;h>=0?h>o:o>h;e=h>=0?++o:--o)s.max=Math.max(s.max,s.buckets[e]);return s},i.prototype.y=function(){return d3.scale.linear().domain(this.options.bucketRange).range([this.innerHeight(),0])},i.prototype.ySvg=function(){return d3.scale.linear().domain(this.options.bucketRange).range([this.innerHeight()/this.pixelRatio,0])},i.prototype.h=function(){return this.innerHeight()/this.options.buckets},i.prototype._offsetX=function(){return.5*this.w()/this.pixelRatio},i.prototype._setupPaintCanvas=function(){return this.paintWidth=(this.options.windowSize+1)*this.w(),this.paintHeight=this.height*this.pixelRatio,this.paint=document.createElement("CANVAS"),this.paint.width=this.paintWidth,this.paint.height=this.paintHeight,this.p=Epoch.Util.getContext(this.paint),this.redraw(),this.on("after:shift","_paintEntry"),this.on("transition:end","_shiftPaintCanvas"),this.on("transition:end",function(t){return function(){return t.draw(t.animation.frame*t.animation.delta())}}(this))},i.prototype.redraw=function(){var t,i;if(Epoch.isNonEmptyArray(this.data)&&Epoch.isNonEmptyArray(this.data[0].values)){for(i=this.data[0].values.length,t=this.options.windowSize,this.inTransition()&&t++;--i>=0&&--t>=0;)this._paintEntry(i,t);return this.draw(this.animation.frame*this.animation.delta())}},i.prototype._computeColor=function(t,i,n){return Epoch.Util.toRGBA(n,this._opacityFn(t,i))},i.prototype._paintEntry=function(t,i){var n,e,r,o,s,a,h,p,l,u,c,g,d,f,y,m,v,x,_,w,k,b,E,C;for(null==t&&(t=null),null==i&&(i=null),v=[this.w(),this.h()],E=v[0],h=v[1],null==t&&(t=this.data[0].values.length-1),null==i&&(i=this.options.windowSize),s=[],e=function(){var t,i,n;for(n=[],p=t=0,i=this.options.buckets;i>=0?i>t:t>i;p=i>=0?++t:--t)n.push(0);return n}.call(this),m=0,x=this.getVisibleLayers(),u=0,g=x.length;g>u;u++){c=x[u],a=this._getBuckets(c.values[t]),_=a.buckets;for(n in _)hasProp.call(_,n)&&(o=_[n],e[n]+=o);m+=a.max,k=this.getStyles("."+c.className.split(" ").join(".")+" rect.bucket"),a.color=k.fill,s.push(a)}C=i*E,this.p.clearRect(C,0,E,this.paintHeight),l=this.options.buckets,w=[];for(n in e)if(hasProp.call(e,n)){for(b=e[n],r=this._avgLab(s,n),y=0,f=0,d=s.length;d>f;f++)a=s[f],y+=a.buckets[n]/b*m;(b>0||this.options.paintZeroValues)&&(this.p.fillStyle=this._computeColor(b,y,r),this.p.fillRect(C,(l-1)*h,E-this.options.bucketPadding,h-this.options.bucketPadding)),w.push(l--)}return w},i.prototype._shiftPaintCanvas=function(){var t;return t=this.p.getImageData(this.w(),0,this.paintWidth-this.w(),this.paintHeight),this.p.putImageData(t,0,0)},i.prototype._avgLab=function(t,i){var n,e,r,o,s,a,h,p,l,u,c,g;for(u=[0,0,0,0],h=u[0],n=u[1],e=u[2],c=u[3],a=0,p=t.length;p>a;a++)o=t[a],null!=o.buckets[i]&&(c+=o.buckets[i]);for(s in t)hasProp.call(t,s)&&(o=t[s],g=null!=o.buckets[i]?0|o.buckets[i]:0,l=g/c,r=d3.lab(o.color),h+=l*r.l,n+=l*r.a,e+=l*r.b);return d3.lab(h,n,e).toString()},i.prototype.draw=function(t){return null==t&&(t=0),this.clear(),this.ctx.drawImage(this.paint,t,0),i.__super__.draw.call(this)},i.prototype.bucketsChanged=function(){return this.redraw()},i.prototype.bucketRangeChanged=function(){return this._transitionRangeAxes(),this.redraw()},i.prototype.opacityChanged=function(){return this._setOpacityFunction(),this.redraw()},i.prototype.bucketPaddingChanged=function(){return this.redraw()},i.prototype.paintZeroValuesChanged=function(){return this.redraw()},i.prototype.cutOutliersChanged=function(){return this.redraw()},i.prototype.layerChanged=function(){return this.redraw()},i}(Epoch.Time.Plot);var extend=function(t,i){function n(){this.constructor=t}for(var e in i)hasProp.call(i,e)&&(t[e]=i[e]);return n.prototype=i.prototype,t.prototype=new n,t.__super__=i.prototype,t},hasProp={}.hasOwnProperty;Epoch.Time.Line=function(t){function i(t){var n;this.options=null!=t?t:{},null==(n=this.options).type&&(n.type="time.line"),i.__super__.constructor.call(this,this.options),this.draw()}return extend(i,t),i.prototype.setStyles=function(t){var i;return i=this.getStyles("g."+t.replace(/\s/g,".")+" path.line"),this.ctx.fillStyle=i.fill,this.ctx.strokeStyle=i.stroke,this.ctx.lineWidth=this.pixelRatio*i["stroke-width"].replace("px","")},i.prototype.draw=function(t){var n,e,r,o,s,a,h,p,l,u,c,g;for(null==t&&(t=0),this.clear(),c=this.w(),p=this.getVisibleLayers(),o=0,h=p.length;h>o;o++)if(a=p[o],Epoch.isNonEmptyArray(a.values)){for(this.setStyles(a.className),this.ctx.beginPath(),g=this.y(a.range),l=[this.options.windowSize,a.values.length,this.inTransition()],r=l[0],s=l[1],u=l[2];--r>=-2&&--s>=0;)e=a.values[s],n=[(r+1)*c+t,g(e.y)],u&&(n[0]+=c),r===this.options.windowSize-1?this.ctx.moveTo.apply(this.ctx,n):this.ctx.lineTo.apply(this.ctx,n);this.ctx.stroke()}return i.__super__.draw.call(this)},i}(Epoch.Time.Plot),Epoch._typeMap={area:Epoch.Chart.Area,bar:Epoch.Chart.Bar,line:Epoch.Chart.Line,pie:Epoch.Chart.Pie,scatter:Epoch.Chart.Scatter,histogram:Epoch.Chart.Histogram,"time.area":Epoch.Time.Area,"time.bar":Epoch.Time.Bar,"time.line":Epoch.Time.Line,"time.gauge":Epoch.Time.Gauge,"time.heatmap":Epoch.Time.Heatmap};var jQueryModule;jQueryModule=function(t){var i;return i="epoch-chart",t.fn.epoch=function(t){var n,e;return t.el=this.get(0),null==(n=this.data(i))&&(e=Epoch._typeMap[t.type],null==e&&Epoch.exception("Unknown chart type '"+t.type+"'"),this.data(i,n=new e(t))),n}},null!=window.jQuery&&jQueryModule(jQuery);var MooToolsModule;MooToolsModule=function(){var t;return t="epoch-chart",Element.implement("epoch",function(i){var n,e,r;return r=$$(this),null==(n=r.retrieve(t)[0])&&(i.el=this,e=Epoch._typeMap[i.type],null==e&&Epoch.exception("Unknown chart type '"+i.type+"'"),r.store(t,n=new e(i))),n})},null!=window.MooTools&&MooToolsModule();var zeptoModule;zeptoModule=function(t){var i,n,e,r;return i="epoch-chart",e={},n=0,r=function(){return i+"-"+ ++n},t.extend(t.fn,{epoch:function(t){var n,o,s;return null!=(o=this.data(i))?e[o]:(t.el=this.get(0),s=Epoch._typeMap[t.type],null==s&&Epoch.exception("Unknown chart type '"+t.type+"'"),this.data(i,o=r()),n=new s(t),e[o]=n,n)}})},null!=window.Zepto&&zeptoModule(Zepto);
diff --git a/modules/http/static/epoch.spdx b/modules/http/static/epoch.spdx
new file mode 100644
index 0000000..c01b497
--- /dev/null
+++ b/modules/http/static/epoch.spdx
@@ -0,0 +1,11 @@
+SPDXVersion: SPDX-2.1
+DataLicense: CC0-1.0
+SPDXID: SPDXRef-DOCUMENT
+DocumentName: epoch
+DocumentNamespace: http://spdx.org/spdxdocs/spdx-v2.1-4efd8b6e-174f-48e4-a228-3059f191c7e8
+
+PackageName: epoch
+PackageVersion: 0.8.3
+PackageDownloadLocation: git+https://github.com/epochjs/epoch.git@47aef0a5aa8458bdd5011d108ab92a560215bc57#dist/
+PackageOriginator: Organization: Fastly
+PackageLicenseDeclared: MIT
diff --git a/modules/http/static/favicon.ico b/modules/http/static/favicon.ico
new file mode 100644
index 0000000..8c85535
--- /dev/null
+++ b/modules/http/static/favicon.ico
Binary files differ
diff --git a/modules/http/static/glyphicons-halflings-regular.spdx b/modules/http/static/glyphicons-halflings-regular.spdx
new file mode 100644
index 0000000..3241fd5
--- /dev/null
+++ b/modules/http/static/glyphicons-halflings-regular.spdx
@@ -0,0 +1,11 @@
+SPDXVersion: SPDX-2.1
+DataLicense: CC0-1.0
+SPDXID: SPDXRef-DOCUMENT
+DocumentName: bootstrap-glyphicons-halflings-regular
+DocumentNamespace: http://spdx.org/spdxdocs/spdx-v2.1-5fdae9d2-c79e-4242-8a82-0909ddd93ae3
+
+PackageName: bootstrap-glyphicons-halflings-regular
+PackageVersion: 3.3.6
+PackageDownloadLocation: git+https://github.com/twbs/bootstrap.git@81df608a40bf0629a1dc08e584849bb1e43e0b7a#dist/fonts/glyphicons-halflings-regular.woff2
+PackageOriginator: Organization: Twitter
+PackageLicenseDeclared: MIT
diff --git a/modules/http/static/glyphicons-halflings-regular.woff2 b/modules/http/static/glyphicons-halflings-regular.woff2
new file mode 100644
index 0000000..64539b5
--- /dev/null
+++ b/modules/http/static/glyphicons-halflings-regular.woff2
Binary files differ
diff --git a/modules/http/static/jquery.js b/modules/http/static/jquery.js
new file mode 100644
index 0000000..bb692f6
--- /dev/null
+++ b/modules/http/static/jquery.js
@@ -0,0 +1,5 @@
+/*! jQuery v2.1.4 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */
+/* SPDX-License-Identifier: MIT */
+!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b="length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\f]' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=ma(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=na(b);function qa(){}qa.prototype=d.filters=d.pseudos,d.setFilters=new qa,g=ga.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=S.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=T.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(R," ")}),h=h.slice(c.length));for(g in d.filter)!(e=X[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?ga.error(a):z(a,i).slice(0)};function ra(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){
+return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?n.queue(this[0],a):void 0===b?this:this.each(function(){var c=n.queue(this,a,b);n._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&n.dequeue(this,a)})},dequeue:function(a){return this.each(function(){n.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=n.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=L.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var Q=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,R=["Top","Right","Bottom","Left"],S=function(a,b){return a=b||a,"none"===n.css(a,"display")||!n.contains(a.ownerDocument,a)},T=/^(?:checkbox|radio)$/i;!function(){var a=l.createDocumentFragment(),b=a.appendChild(l.createElement("div")),c=l.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button;return null==a.pageX&&null!=b.clientX&&(c=a.target.ownerDocument||l,d=c.documentElement,e=c.body,a.pageX=b.clientX+(d&&d.scrollLeft||e&&e.scrollLeft||0)-(d&&d.clientLeft||e&&e.clientLeft||0),a.pageY=b.clientY+(d&&d.scrollTop||e&&e.scrollTop||0)-(d&&d.clientTop||e&&e.clientTop||0)),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},fix:function(a){if(a[n.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=W.test(e)?this.mouseHooks:V.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new n.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=l),3===a.target.nodeType&&(a.target=a.target.parentNode),g.filter?g.filter(a,f):a},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==_()&&this.focus?(this.focus(),!1):void 0},delegateType:"focusin"},blur:{trigger:function(){return this===_()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&n.nodeName(this,"input")?(this.click(),!1):void 0},_default:function(a){return n.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=n.extend(new n.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?n.event.trigger(e,null,b):n.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},n.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)},n.Event=function(a,b){return this instanceof n.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?Z:$):this.type=a,b&&n.extend(this,b),this.timeStamp=a&&a.timeStamp||n.now(),void(this[n.expando]=!0)):new n.Event(a,b)},n.Event.prototype={isDefaultPrevented:$,isPropagationStopped:$,isImmediatePropagationStopped:$,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=Z,a&&a.preventDefault&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=Z,a&&a.stopPropagation&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=Z,a&&a.stopImmediatePropagation&&a.stopImmediatePropagation(),this.stopPropagation()}},n.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){n.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!n.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),k.focusinBubbles||n.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){n.event.simulate(b,a.target,n.event.fix(a),!0)};n.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=L.access(d,b);e||d.addEventListener(a,c,!0),L.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=L.access(d,b)-1;e?L.access(d,b,e):(d.removeEventListener(a,c,!0),L.remove(d,b))}}}),n.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(g in a)this.on(g,b,c,a[g],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=$;else if(!d)return this;return 1===e&&(f=d,d=function(a){return n().off(a),f.apply(this,arguments)},d.guid=f.guid||(f.guid=n.guid++)),this.each(function(){n.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,n(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=$),this.each(function(){n.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){n.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?n.event.trigger(a,b,c,!0):void 0}});var aa=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,ia={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1></$2>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=qa[0].contentDocument,b.write(),b.close(),c=sa(a,b),qa.detach()),ra[a]=c),c}var ua=/^margin/,va=new RegExp("^("+Q+")(?!px)[a-z%]+$","i"),wa=function(b){return b.ownerDocument.defaultView.opener?b.ownerDocument.defaultView.getComputedStyle(b,null):a.getComputedStyle(b,null)};function xa(a,b,c){var d,e,f,g,h=a.style;return c=c||wa(a),c&&(g=c.getPropertyValue(b)||c[b]),c&&(""!==g||n.contains(a.ownerDocument,a)||(g=n.style(a,b)),va.test(g)&&ua.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+"":g}function ya(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d=l.documentElement,e=l.createElement("div"),f=l.createElement("div");if(f.style){f.style.backgroundClip="content-box",f.cloneNode(!0).style.backgroundClip="",k.clearCloneStyle="content-box"===f.style.backgroundClip,e.style.cssText="border:0;width:0;height:0;top:0;left:-9999px;margin-top:1px;position:absolute",e.appendChild(f);function g(){f.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute",f.innerHTML="",d.appendChild(e);var g=a.getComputedStyle(f,null);b="1%"!==g.top,c="4px"===g.width,d.removeChild(e)}a.getComputedStyle&&n.extend(k,{pixelPosition:function(){return g(),b},boxSizingReliable:function(){return null==c&&g(),c},reliableMarginRight:function(){var b,c=f.appendChild(l.createElement("div"));return c.style.cssText=f.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",c.style.marginRight=c.style.width="0",f.style.width="1px",d.appendChild(e),b=!parseFloat(a.getComputedStyle(c,null).marginRight),d.removeChild(e),f.removeChild(c),b}})}}(),n.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var za=/^(none|table(?!-c[ea]).+)/,Aa=new RegExp("^("+Q+")(.*)$","i"),Ba=new RegExp("^([+-])=("+Q+")","i"),Ca={position:"absolute",visibility:"hidden",display:"block"},Da={letterSpacing:"0",fontWeight:"400"},Ea=["Webkit","O","Moz","ms"];function Fa(a,b){if(b in a)return b;var c=b[0].toUpperCase()+b.slice(1),d=b,e=Ea.length;while(e--)if(b=Ea[e]+c,b in a)return b;return d}function Ga(a,b,c){var d=Aa.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Ha(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=n.css(a,c+R[f],!0,e)),d?("content"===c&&(g-=n.css(a,"padding"+R[f],!0,e)),"margin"!==c&&(g-=n.css(a,"border"+R[f]+"Width",!0,e))):(g+=n.css(a,"padding"+R[f],!0,e),"padding"!==c&&(g+=n.css(a,"border"+R[f]+"Width",!0,e)));return g}function Ia(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=wa(a),g="border-box"===n.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=xa(a,b,f),(0>e||null==e)&&(e=a.style[b]),va.test(e))return e;d=g&&(k.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Ha(a,b,c||(g?"border":"content"),d,f)+"px"}function Ja(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=L.get(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&S(d)&&(f[g]=L.access(d,"olddisplay",ta(d.nodeName)))):(e=S(d),"none"===c&&e||L.set(d,"olddisplay",e?c:n.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}n.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=xa(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=n.camelCase(b),i=a.style;return b=n.cssProps[h]||(n.cssProps[h]=Fa(i,h)),g=n.cssHooks[b]||n.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b]:(f=typeof c,"string"===f&&(e=Ba.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(n.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||n.cssNumber[h]||(c+="px"),k.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=n.camelCase(b);return b=n.cssProps[h]||(n.cssProps[h]=Fa(a.style,h)),g=n.cssHooks[b]||n.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=xa(a,b,d)),"normal"===e&&b in Da&&(e=Da[b]),""===c||c?(f=parseFloat(e),c===!0||n.isNumeric(f)?f||0:e):e}}),n.each(["height","width"],function(a,b){n.cssHooks[b]={get:function(a,c,d){return c?za.test(n.css(a,"display"))&&0===a.offsetWidth?n.swap(a,Ca,function(){return Ia(a,b,d)}):Ia(a,b,d):void 0},set:function(a,c,d){var e=d&&wa(a);return Ga(a,c,d?Ha(a,b,d,"border-box"===n.css(a,"boxSizing",!1,e),e):0)}}}),n.cssHooks.marginRight=ya(k.reliableMarginRight,function(a,b){return b?n.swap(a,{display:"inline-block"},xa,[a,"marginRight"]):void 0}),n.each({margin:"",padding:"",border:"Width"},function(a,b){n.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+R[d]+b]=f[d]||f[d-2]||f[0];return e}},ua.test(a)||(n.cssHooks[a+b].set=Ga)}),n.fn.extend({css:function(a,b){return J(this,function(a,b,c){var d,e,f={},g=0;if(n.isArray(b)){for(d=wa(a),e=b.length;e>g;g++)f[b[g]]=n.css(a,b[g],!1,d);return f}return void 0!==c?n.style(a,b,c):n.css(a,b)},a,b,arguments.length>1)},show:function(){return Ja(this,!0)},hide:function(){return Ja(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){S(this)?n(this).show():n(this).hide()})}});function Ka(a,b,c,d,e){return new Ka.prototype.init(a,b,c,d,e)}n.Tween=Ka,Ka.prototype={constructor:Ka,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(n.cssNumber[c]?"":"px")},cur:function(){var a=Ka.propHooks[this.prop];return a&&a.get?a.get(this):Ka.propHooks._default.get(this)},run:function(a){var b,c=Ka.propHooks[this.prop];return this.options.duration?this.pos=b=n.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Ka.propHooks._default.set(this),this}},Ka.prototype.init.prototype=Ka.prototype,Ka.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=n.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){n.fx.step[a.prop]?n.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[n.cssProps[a.prop]]||n.cssHooks[a.prop])?n.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Ka.propHooks.scrollTop=Ka.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},n.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},n.fx=Ka.prototype.init,n.fx.step={};var La,Ma,Na=/^(?:toggle|show|hide)$/,Oa=new RegExp("^(?:([+-])=|)("+Q+")([a-z%]*)$","i"),Pa=/queueHooks$/,Qa=[Va],Ra={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=Oa.exec(b),f=e&&e[3]||(n.cssNumber[a]?"":"px"),g=(n.cssNumber[a]||"px"!==f&&+d)&&Oa.exec(n.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,n.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function Sa(){return setTimeout(function(){La=void 0}),La=n.now()}function Ta(a,b){var c,d=0,e={height:a};for(b=b?1:0;4>d;d+=2-b)c=R[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function Ua(a,b,c){for(var d,e=(Ra[b]||[]).concat(Ra["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function Va(a,b,c){var d,e,f,g,h,i,j,k,l=this,m={},o=a.style,p=a.nodeType&&S(a),q=L.get(a,"fxshow");c.queue||(h=n._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,l.always(function(){l.always(function(){h.unqueued--,n.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=n.css(a,"display"),k="none"===j?L.get(a,"olddisplay")||ta(a.nodeName):j,"inline"===k&&"none"===n.css(a,"float")&&(o.display="inline-block")),c.overflow&&(o.overflow="hidden",l.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],Na.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(p?"hide":"show")){if("show"!==e||!q||void 0===q[d])continue;p=!0}m[d]=q&&q[d]||n.style(a,d)}else j=void 0;if(n.isEmptyObject(m))"inline"===("none"===j?ta(a.nodeName):j)&&(o.display=j);else{q?"hidden"in q&&(p=q.hidden):q=L.access(a,"fxshow",{}),f&&(q.hidden=!p),p?n(a).show():l.done(function(){n(a).hide()}),l.done(function(){var b;L.remove(a,"fxshow");for(b in m)n.style(a,b,m[b])});for(d in m)g=Ua(p?q[d]:0,d,l),d in q||(q[d]=g.start,p&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function Wa(a,b){var c,d,e,f,g;for(c in a)if(d=n.camelCase(c),e=b[d],f=a[c],n.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=n.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function Xa(a,b,c){var d,e,f=0,g=Qa.length,h=n.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=La||Sa(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:n.extend({},b),opts:n.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:La||Sa(),duration:c.duration,tweens:[],createTween:function(b,c){var d=n.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(Wa(k,j.opts.specialEasing);g>f;f++)if(d=Qa[f].call(j,a,k,j.opts))return d;return n.map(k,Ua,j),n.isFunction(j.opts.start)&&j.opts.start.call(a,j),n.fx.timer(n.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}n.Animation=n.extend(Xa,{tweener:function(a,b){n.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],Ra[c]=Ra[c]||[],Ra[c].unshift(b)},prefilter:function(a,b){b?Qa.unshift(a):Qa.push(a)}}),n.speed=function(a,b,c){var d=a&&"object"==typeof a?n.extend({},a):{complete:c||!c&&b||n.isFunction(a)&&a,duration:a,easing:c&&b||b&&!n.isFunction(b)&&b};return d.duration=n.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in n.fx.speeds?n.fx.speeds[d.duration]:n.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){n.isFunction(d.old)&&d.old.call(this),d.queue&&n.dequeue(this,d.queue)},d},n.fn.extend({fadeTo:function(a,b,c,d){return this.filter(S).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=n.isEmptyObject(a),f=n.speed(b,c,d),g=function(){var b=Xa(this,n.extend({},a),f);(e||L.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=n.timers,g=L.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&Pa.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&n.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=L.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=n.timers,g=d?d.length:0;for(c.finish=!0,n.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),n.each(["toggle","show","hide"],function(a,b){var c=n.fn[b];n.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(Ta(b,!0),a,d,e)}}),n.each({slideDown:Ta("show"),slideUp:Ta("hide"),slideToggle:Ta("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){n.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),n.timers=[],n.fx.tick=function(){var a,b=0,c=n.timers;for(La=n.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||n.fx.stop(),La=void 0},n.fx.timer=function(a){n.timers.push(a),a()?n.fx.start():n.timers.pop()},n.fx.interval=13,n.fx.start=function(){Ma||(Ma=setInterval(n.fx.tick,n.fx.interval))},n.fx.stop=function(){clearInterval(Ma),Ma=null},n.fx.speeds={slow:600,fast:200,_default:400},n.fn.delay=function(a,b){return a=n.fx?n.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a=l.createElement("input"),b=l.createElement("select"),c=b.appendChild(l.createElement("option"));a.type="checkbox",k.checkOn=""!==a.value,k.optSelected=c.selected,b.disabled=!0,k.optDisabled=!c.disabled,a=l.createElement("input"),a.value="t",a.type="radio",k.radioValue="t"===a.value}();var Ya,Za,$a=n.expr.attrHandle;n.fn.extend({attr:function(a,b){return J(this,n.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){n.removeAttr(this,a)})}}),n.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===U?n.prop(a,b,c):(1===f&&n.isXMLDoc(a)||(b=b.toLowerCase(),d=n.attrHooks[b]||(n.expr.match.bool.test(b)?Za:Ya)),
+void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=n.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void n.removeAttr(a,b))},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=n.propFix[c]||c,n.expr.match.bool.test(c)&&(a[d]=!1),a.removeAttribute(c)},attrHooks:{type:{set:function(a,b){if(!k.radioValue&&"radio"===b&&n.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),Za={set:function(a,b,c){return b===!1?n.removeAttr(a,c):a.setAttribute(c,c),c}},n.each(n.expr.match.bool.source.match(/\w+/g),function(a,b){var c=$a[b]||n.find.attr;$a[b]=function(a,b,d){var e,f;return d||(f=$a[b],$a[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,$a[b]=f),e}});var _a=/^(?:input|select|textarea|button)$/i;n.fn.extend({prop:function(a,b){return J(this,n.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[n.propFix[a]||a]})}}),n.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!n.isXMLDoc(a),f&&(b=n.propFix[b]||b,e=n.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){return a.hasAttribute("tabindex")||_a.test(a.nodeName)||a.href?a.tabIndex:-1}}}}),k.optSelected||(n.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null}}),n.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){n.propFix[this.toLowerCase()]=this});var ab=/[\t\r\n\f]/g;n.fn.extend({addClass:function(a){var b,c,d,e,f,g,h="string"==typeof a&&a,i=0,j=this.length;if(n.isFunction(a))return this.each(function(b){n(this).addClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ab," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=n.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0===arguments.length||"string"==typeof a&&a,i=0,j=this.length;if(n.isFunction(a))return this.each(function(b){n(this).removeClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ab," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?n.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(n.isFunction(a)?function(c){n(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=n(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===U||"boolean"===c)&&(this.className&&L.set(this,"__className__",this.className),this.className=this.className||a===!1?"":L.get(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(ab," ").indexOf(b)>=0)return!0;return!1}});var bb=/\r/g;n.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=n.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,n(this).val()):a,null==e?e="":"number"==typeof e?e+="":n.isArray(e)&&(e=n.map(e,function(a){return null==a?"":a+""})),b=n.valHooks[this.type]||n.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=n.valHooks[e.type]||n.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(bb,""):null==c?"":c)}}}),n.extend({valHooks:{option:{get:function(a){var b=n.find.attr(a,"value");return null!=b?b:n.trim(n.text(a))}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(k.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&n.nodeName(c.parentNode,"optgroup"))){if(b=n(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=n.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=n.inArray(d.value,f)>=0)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),n.each(["radio","checkbox"],function(){n.valHooks[this]={set:function(a,b){return n.isArray(b)?a.checked=n.inArray(n(a).val(),b)>=0:void 0}},k.checkOn||(n.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})}),n.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){n.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),n.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var cb=n.now(),db=/\?/;n.parseJSON=function(a){return JSON.parse(a+"")},n.parseXML=function(a){var b,c;if(!a||"string"!=typeof a)return null;try{c=new DOMParser,b=c.parseFromString(a,"text/xml")}catch(d){b=void 0}return(!b||b.getElementsByTagName("parsererror").length)&&n.error("Invalid XML: "+a),b};var eb=/#.*$/,fb=/([?&])_=[^&]*/,gb=/^(.*?):[ \t]*([^\r\n]*)$/gm,hb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,ib=/^(?:GET|HEAD)$/,jb=/^\/\//,kb=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,lb={},mb={},nb="*/".concat("*"),ob=a.location.href,pb=kb.exec(ob.toLowerCase())||[];function qb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(n.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function rb(a,b,c,d){var e={},f=a===mb;function g(h){var i;return e[h]=!0,n.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function sb(a,b){var c,d,e=n.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&n.extend(!0,a,d),a}function tb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function ub(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}n.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:ob,type:"GET",isLocal:hb.test(pb[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":nb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":n.parseJSON,"text xml":n.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?sb(sb(a,n.ajaxSettings),b):sb(n.ajaxSettings,a)},ajaxPrefilter:qb(lb),ajaxTransport:qb(mb),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=n.ajaxSetup({},b),l=k.context||k,m=k.context&&(l.nodeType||l.jquery)?n(l):n.event,o=n.Deferred(),p=n.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!f){f={};while(b=gb.exec(e))f[b[1].toLowerCase()]=b[2]}b=f[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?e:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return c&&c.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||ob)+"").replace(eb,"").replace(jb,pb[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=n.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(h=kb.exec(k.url.toLowerCase()),k.crossDomain=!(!h||h[1]===pb[1]&&h[2]===pb[2]&&(h[3]||("http:"===h[1]?"80":"443"))===(pb[3]||("http:"===pb[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=n.param(k.data,k.traditional)),rb(lb,k,b,v),2===t)return v;i=n.event&&k.global,i&&0===n.active++&&n.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!ib.test(k.type),d=k.url,k.hasContent||(k.data&&(d=k.url+=(db.test(d)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=fb.test(d)?d.replace(fb,"$1_="+cb++):d+(db.test(d)?"&":"?")+"_="+cb++)),k.ifModified&&(n.lastModified[d]&&v.setRequestHeader("If-Modified-Since",n.lastModified[d]),n.etag[d]&&v.setRequestHeader("If-None-Match",n.etag[d])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+nb+"; q=0.01":""):k.accepts["*"]);for(j in k.headers)v.setRequestHeader(j,k.headers[j]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(j in{success:1,error:1,complete:1})v[j](k[j]);if(c=rb(mb,k,b,v)){v.readyState=1,i&&m.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,c.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,f,h){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),c=void 0,e=h||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,f&&(u=tb(k,v,f)),u=ub(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(n.lastModified[d]=w),w=v.getResponseHeader("etag"),w&&(n.etag[d]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,i&&m.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),i&&(m.trigger("ajaxComplete",[v,k]),--n.active||n.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return n.get(a,b,c,"json")},getScript:function(a,b){return n.get(a,void 0,b,"script")}}),n.each(["get","post"],function(a,b){n[b]=function(a,c,d,e){return n.isFunction(c)&&(e=e||d,d=c,c=void 0),n.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),n._evalUrl=function(a){return n.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},n.fn.extend({wrapAll:function(a){var b;return n.isFunction(a)?this.each(function(b){n(this).wrapAll(a.call(this,b))}):(this[0]&&(b=n(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this)},wrapInner:function(a){return this.each(n.isFunction(a)?function(b){n(this).wrapInner(a.call(this,b))}:function(){var b=n(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=n.isFunction(a);return this.each(function(c){n(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){n.nodeName(this,"body")||n(this).replaceWith(this.childNodes)}).end()}}),n.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0},n.expr.filters.visible=function(a){return!n.expr.filters.hidden(a)};var vb=/%20/g,wb=/\[\]$/,xb=/\r?\n/g,yb=/^(?:submit|button|image|reset|file)$/i,zb=/^(?:input|select|textarea|keygen)/i;function Ab(a,b,c,d){var e;if(n.isArray(b))n.each(b,function(b,e){c||wb.test(a)?d(a,e):Ab(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==n.type(b))d(a,b);else for(e in b)Ab(a+"["+e+"]",b[e],c,d)}n.param=function(a,b){var c,d=[],e=function(a,b){b=n.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=n.ajaxSettings&&n.ajaxSettings.traditional),n.isArray(a)||a.jquery&&!n.isPlainObject(a))n.each(a,function(){e(this.name,this.value)});else for(c in a)Ab(c,a[c],b,e);return d.join("&").replace(vb,"+")},n.fn.extend({serialize:function(){return n.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=n.prop(this,"elements");return a?n.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!n(this).is(":disabled")&&zb.test(this.nodeName)&&!yb.test(a)&&(this.checked||!T.test(a))}).map(function(a,b){var c=n(this).val();return null==c?null:n.isArray(c)?n.map(c,function(a){return{name:b.name,value:a.replace(xb,"\r\n")}}):{name:b.name,value:c.replace(xb,"\r\n")}}).get()}}),n.ajaxSettings.xhr=function(){try{return new XMLHttpRequest}catch(a){}};var Bb=0,Cb={},Db={0:200,1223:204},Eb=n.ajaxSettings.xhr();a.attachEvent&&a.attachEvent("onunload",function(){for(var a in Cb)Cb[a]()}),k.cors=!!Eb&&"withCredentials"in Eb,k.ajax=Eb=!!Eb,n.ajaxTransport(function(a){var b;return k.cors||Eb&&!a.crossDomain?{send:function(c,d){var e,f=a.xhr(),g=++Bb;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)f.setRequestHeader(e,c[e]);b=function(a){return function(){b&&(delete Cb[g],b=f.onload=f.onerror=null,"abort"===a?f.abort():"error"===a?d(f.status,f.statusText):d(Db[f.status]||f.status,f.statusText,"string"==typeof f.responseText?{text:f.responseText}:void 0,f.getAllResponseHeaders()))}},f.onload=b(),f.onerror=b("error"),b=Cb[g]=b("abort");try{f.send(a.hasContent&&a.data||null)}catch(h){if(b)throw h}},abort:function(){b&&b()}}:void 0}),n.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return n.globalEval(a),a}}}),n.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),n.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(d,e){b=n("<script>").prop({async:!0,charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&e("error"===a.type?404:200,a.type)}),l.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Fb=[],Gb=/(=)\?(?=&|$)|\?\?/;n.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Fb.pop()||n.expando+"_"+cb++;return this[a]=!0,a}}),n.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Gb.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Gb.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=n.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Gb,"$1"+e):b.jsonp!==!1&&(b.url+=(db.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||n.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Fb.push(e)),g&&n.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),n.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||l;var d=v.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=n.buildFragment([a],b,e),e&&e.length&&n(e).remove(),n.merge([],d.childNodes))};var Hb=n.fn.load;n.fn.load=function(a,b,c){if("string"!=typeof a&&Hb)return Hb.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=n.trim(a.slice(h)),a=a.slice(0,h)),n.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&n.ajax({url:a,type:e,dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?n("<div>").append(n.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,f||[a.responseText,b,a])}),this},n.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){n.fn[b]=function(a){return this.on(b,a)}}),n.expr.filters.animated=function(a){return n.grep(n.timers,function(b){return a===b.elem}).length};var Ib=a.document.documentElement;function Jb(a){return n.isWindow(a)?a:9===a.nodeType&&a.defaultView}n.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=n.css(a,"position"),l=n(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=n.css(a,"top"),i=n.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),n.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},n.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){n.offset.setOffset(this,a,b)});var b,c,d=this[0],e={top:0,left:0},f=d&&d.ownerDocument;if(f)return b=f.documentElement,n.contains(b,d)?(typeof d.getBoundingClientRect!==U&&(e=d.getBoundingClientRect()),c=Jb(f),{top:e.top+c.pageYOffset-b.clientTop,left:e.left+c.pageXOffset-b.clientLeft}):e},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===n.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),n.nodeName(a[0],"html")||(d=a.offset()),d.top+=n.css(a[0],"borderTopWidth",!0),d.left+=n.css(a[0],"borderLeftWidth",!0)),{top:b.top-d.top-n.css(c,"marginTop",!0),left:b.left-d.left-n.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||Ib;while(a&&!n.nodeName(a,"html")&&"static"===n.css(a,"position"))a=a.offsetParent;return a||Ib})}}),n.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(b,c){var d="pageYOffset"===c;n.fn[b]=function(e){return J(this,function(b,e,f){var g=Jb(b);return void 0===f?g?g[c]:b[e]:void(g?g.scrollTo(d?a.pageXOffset:f,d?f:a.pageYOffset):b[e]=f)},b,e,arguments.length,null)}}),n.each(["top","left"],function(a,b){n.cssHooks[b]=ya(k.pixelPosition,function(a,c){return c?(c=xa(a,b),va.test(c)?n(a).position()[b]+"px":c):void 0})}),n.each({Height:"height",Width:"width"},function(a,b){n.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){n.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return J(this,function(b,c,d){var e;return n.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?n.css(b,c,g):n.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),n.fn.size=function(){return this.length},n.fn.andSelf=n.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return n});var Kb=a.jQuery,Lb=a.$;return n.noConflict=function(b){return a.$===n&&(a.$=Lb),b&&a.jQuery===n&&(a.jQuery=Kb),n},typeof b===U&&(a.jQuery=a.$=n),n});
diff --git a/modules/http/static/jquery.spdx b/modules/http/static/jquery.spdx
new file mode 100644
index 0000000..af3cbb4
--- /dev/null
+++ b/modules/http/static/jquery.spdx
@@ -0,0 +1,12 @@
+SPDXVersion: SPDX-2.1
+DataLicense: CC0-1.0
+SPDXID: SPDXRef-DOCUMENT
+DocumentName: jquery
+DocumentNamespace: http://spdx.org/spdxdocs/spdx-v2.1-d1bf4e73-cd79-434a-ba6c-834967690525
+
+PackageName: jquery
+PackageVersion: 2.1.4
+PackageDownloadLocation: https://code.jquery.com/jquery-2.1.4.min.js
+PackageChecksum: SHA256: f16ab224bb962910558715c82f58c10c3ed20f153ddfaa199029f141b5b0255c
+PackageOriginator: Organization: jQuery Foundation
+PackageLicenseDeclared: MIT
diff --git a/modules/http/static/kresd.css b/modules/http/static/kresd.css
new file mode 100644
index 0000000..16238a4
--- /dev/null
+++ b/modules/http/static/kresd.css
@@ -0,0 +1,44 @@
+/*
+ * Base structure
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/* Move down content because we have a fixed navbar that is 50px tall */
+body {
+ padding-top: 50px;
+}
+
+/*
+ * Tags and labels.
+ */
+.tag {
+ margin: 0 3px 3px 0;
+}
+
+.tag-default {
+ background-color: #efefef !important;
+ color: #000 !important;
+}
+
+.tag-warning {
+ background-color: #f0ad4e !important;
+ border-color: #eea236 !important;
+ color: #fff !important;
+}
+.tag-success {
+ background-color: #5cb85c !important;
+ border-color: #4cae4c !important;
+ color: #fff !important;
+}
+
+.spark {
+ display: inline-block;
+}
+
+.spark-legend {
+ display: inline-block;
+}
+
+.dygraph-legend {
+ text-align: right;
+}
diff --git a/modules/http/static/kresd.js b/modules/http/static/kresd.js
new file mode 100644
index 0000000..6a6dab1
--- /dev/null
+++ b/modules/http/static/kresd.js
@@ -0,0 +1,367 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+var colours = ["#081d58", "#253494", "#225ea8", "#1d91c0", "#41b6c4", "#7fcdbb", "#c7e9b4", "#edf8b1", "#edf8b1"];
+var latency = ["slow", "1500ms", "1000ms", "500ms", "250ms", "100ms", "50ms", "10ms", "1ms"];
+var Socket = "MozWebSocket" in window ? MozWebSocket : WebSocket;
+let isGraphPaused = false;
+
+$(function() {
+ /* Helper functions */
+ function colorBracket(rtt) {
+ for (var i = latency.length - 1; i >= 0; i--) {
+ if (rtt <= parseInt(latency[i])) {
+ return 'q' + i;
+ }
+ }
+ return 'q8';
+ }
+ function toGeokey(lon, lat) {
+ return lon.toFixed(0)+'#'+lat.toFixed(0);
+ }
+ function updateVisibility(graph, metrics, id, toggle) {
+ /* Some labels are aggregates */
+ if (metrics[id] == null) {
+ for (var key in metrics) {
+ const m = metrics[key];
+ if (m.length > 3 && m[3] == id) {
+ graph.setVisibility(m[0], toggle);
+ }
+ }
+ } else {
+ graph.setVisibility(metrics[id][0], toggle);
+ }
+ }
+ function formatNumber(n) {
+ with (Math) {
+ var base = floor(log(abs(n))/log(1000));
+ var suffix = 'KMB'[base-1];
+ return suffix ? String(n/pow(1000,base)).substring(0,3)+suffix : ''+n;
+ }
+ }
+
+ /* Initialize snippets. */
+ $('section').each(function () {
+ const heading = $(this).find('h2');
+ $('#modules-dropdown').append('<li><a href="#'+this.id+'">'+heading.text()+'</a></li>');
+ });
+
+ /* Render other interesting metrics as lines (hidden by default) */
+ var data = [];
+ var last_metric = 17;
+ var metrics = {
+ 'answer.noerror': [0, 'NOERROR', null, 'By RCODE'],
+ 'answer.nodata': [1, 'NODATA', null, 'By RCODE'],
+ 'answer.nxdomain': [2, 'NXDOMAIN', null, 'By RCODE'],
+ 'answer.servfail': [3, 'SERVFAIL', null, 'By RCODE'],
+ 'answer.dnssec': [4, 'DNSSEC', null, 'By RCODE'],
+ 'cache.hit': [5, 'Cache hit'],
+ 'cache.miss': [6, 'Cache miss'],
+ 'cache.insert': [7, 'Cache insert'],
+ 'cache.delete': [8, 'Cache delete'],
+ 'worker.udp': [9, 'UDP queries'],
+ 'worker.tcp': [10, 'TCP queries'],
+ 'worker.ipv4': [11, 'IPv4 queries'],
+ 'worker.ipv6': [12, 'IPv6 queries'],
+ 'worker.concurrent': [13, 'Concurrent requests'],
+ 'worker.queries': [14, 'Queries received/s'],
+ 'worker.dropped': [15, 'Queries dropped'],
+ 'worker.usertime': [16, 'CPU (user)', null, 'Workers'],
+ 'worker.systime': [17, 'CPU (sys)', null, 'Workers'],
+ };
+
+ /* Render latency metrics as sort of a heatmap */
+ var series = {};
+ for (var i in latency) {
+ const name = 'RTT '+latency[i];
+ const colour = colours[colours.length - i - 1];
+ last_metric = last_metric + 1;
+ metrics['answer.'+latency[i]] = [last_metric, name, colour, 'latency'];
+ series[name] = {fillGraph: true, color: colour, fillAlpha: 1.0};
+ }
+ var labels = ['x'];
+ var visibility = [];
+ for (var key in metrics) {
+ labels.push(metrics[key][1]);
+ visibility.push(false);
+ }
+
+ /* Define how graph looks like. */
+ const graphContainer = $('#stats');
+ const graph = new Dygraph(
+ document.getElementById("chart"),
+ data, {
+ labels: labels,
+ labelsUTC: true,
+ labelsShowZeroValues: false,
+ visibility: visibility,
+ axes: { y: {
+ axisLabelFormatter: function(d) {
+ return formatNumber(d) + 'pps';
+ },
+ }},
+ series: series,
+ strokeWidth: 1,
+ highlightSeriesOpts: {
+ strokeWidth: 3,
+ strokeBorderWidth: 1,
+ highlightCircleSize: 5,
+ },
+ });
+ /* Define metric selector */
+ const chartSelector = $('#chart-selector').selectize({
+ maxItems: null,
+ create: false,
+ onItemAdd: function (x) { updateVisibility(graph, metrics, x, true); },
+ onItemRemove: function (x) { updateVisibility(graph, metrics, x, false); }
+ })[0].selectize;
+ for (var key in metrics) {
+ const m = metrics[key];
+ const groupid = m.length > 3 ? m[3] : key.split('.')[0];
+ const group = m.length > 3 ? m[3] : m[1].split(' ')[0];
+ /* Latency has a special aggregated item */
+ if (group != 'latency') {
+ chartSelector.addOptionGroup(groupid, { label: group } );
+ chartSelector.addOption({ text: m[1], value: key, optgroup: groupid });
+ }
+ }
+ /* Add latency as default */
+ chartSelector.addOption({ text: 'Latency', value: 'latency', optgroup: 'Queries' });
+ chartSelector.addItem('latency');
+ /* Add stacked graph control */
+ $('#chart-stacked').on('change', function(e) {
+ graph.updateOptions({stackedGraph: this.checked});
+ }).click();
+
+ /* Data map */
+ var fills = { defaultFill: '#F5F5F5' };
+ for (var i in colours) {
+ fills['q' + i] = colours[i];
+ }
+ const map = new Datamap({
+ element: document.getElementById('map'),
+ fills: fills,
+ data: {},
+ height: 400,
+ geographyConfig: {
+ highlightOnHover: false,
+ borderColor: '#ccc',
+ borderWidth: 0.5,
+ popupTemplate: function(geo, data) {
+ return ['<div class="hoverinfo">',
+ '<strong>', geo.properties.name, '</strong>',
+ '<br>Queries: <strong>', data ? data.queries : '0', '</strong>',
+ '</div>'].join('');
+ }
+ },
+ bubblesConfig: {
+ popupTemplate: function(geo, data) {
+ return ['<div class="hoverinfo">',
+ '<strong>', data.name, '</strong>',
+ '<br>Queries: <strong>', data ? data.queries : '0', '</strong>',
+ '<br>Average RTT: <strong>', data ? parseInt(data.rtt) : '0', ' ms</strong>',
+ '</div>'].join('');
+ }
+ }
+ });
+
+ /* Realtime updates over WebSockets */
+ function pushMetrics(resp, now, buffer) {
+ var line = new Array(labels.length);
+ line[0] = new Date(now * 1000);
+ for (var lb in resp) {
+ /* Push new datapoints */
+ const metric = metrics[lb];
+ if (metric) {
+ line[metric[0] + 1] = resp[lb];
+ }
+ }
+ /* Buffer graph changes. */
+ data.push(line);
+ if (data.length > 1000) {
+ data.shift();
+ }
+ if ( !buffer ) {
+ if ( !isGraphPaused ) {
+ graph.updateOptions( { 'file': data } );
+ }
+ }
+ }
+
+ var age = 0;
+ var bubbles = [];
+ var bubblemap = {};
+ function pushUpstreams(resp) {
+ if (resp == null) {
+ $('#map-container').hide();
+ return;
+ } else {
+ $('#map-container').show();
+ }
+ /* Get current maximum number of queries for bubble diameter adjustment */
+ var maxQueries = 1;
+ for (var key in resp) {
+ var val = resp[key];
+ if ('data' in val) {
+ maxQueries = Math.max(maxQueries, resp[key].data.length)
+ }
+ }
+ /* Update bubbles and prune the oldest */
+ for (var key in resp) {
+ var val = resp[key];
+ if (!val.data || !val.location || val.location.longitude == null) {
+ continue;
+ }
+ var sum = val.data.reduce(function(a, b) { return a + b; });
+ var avg = sum / val.data.length;
+ var geokey = toGeokey(val.location.longitude, val.location.latitude)
+ var found = bubblemap[geokey];
+ if (!found) {
+ found = {
+ name: [key],
+ longitude: val.location.longitude,
+ latitude: val.location.latitude,
+ queries: 0,
+ rtt: avg,
+ }
+ bubbles.push(found);
+ bubblemap[geokey] = found;
+ }
+ /* Update bubble parameters */
+ if (!(key in found.name)) {
+ found.name.push(key);
+ }
+ found.rtt = (found.rtt + avg) / 2.0;
+ found.fillKey = colorBracket(found.rtt);
+ found.queries = found.queries + val.data.length;
+ found.radius = Math.max(5, 15*(val.data.length/maxQueries));
+ found.age = age;
+ }
+ /* Prune bubbles not updated in a while. */
+ for (var i in bubbles) {
+ var b = bubbles[i];
+ if (b.age <= age - 5) {
+ bubbles.splice(i, 1)
+ bubblemap[i] = null;
+ }
+ }
+ map.bubbles(bubbles);
+ age = age + 1;
+ }
+
+ /* Per-worker information */
+ function updateRate(x, y, dt) {
+ return (100.0 * ((x - y) / dt)).toFixed(1);
+ }
+ function updateWorker(row, next, data, timestamp, buffer) {
+ const dt = timestamp - data.timestamp;
+ const cell = row.find('td');
+ /* Update spark lines and CPU times first */
+ if (dt > 0.0) {
+ const utimeRate = updateRate(next.usertime, data.last.usertime, dt);
+ const stimeRate = updateRate(next.systime, data.last.systime, dt);
+ cell.eq(1).find('span').text(utimeRate + '% / ' + stimeRate + '%');
+ /* Update sparkline graph */
+ data.data.push([new Date(timestamp * 1000), Number(utimeRate), Number(stimeRate)]);
+ if (data.data.length > 60) {
+ data.data.shift();
+ }
+ if (!buffer) {
+ data.graph.updateOptions( { 'file': data.data } );
+ }
+ }
+ /* Update other fields */
+ if (!buffer) {
+ cell.eq(2).text(formatNumber(next.rss) + 'B');
+ cell.eq(3).text(next.pagefaults);
+ cell.eq(4).text('Healthy').addClass('text-success');
+ }
+ }
+
+ var workerData = {};
+ function pushWorkers(resp, timestamp, buffer) {
+ if (resp == null) {
+ return;
+ }
+ const workerTable = $('#workers');
+ for (var pid in resp) {
+ var row = workerTable.find('tr[data-pid='+pid+']');
+ if (row.length == 0) {
+ row = workerTable.append(
+ '<tr data-pid='+pid+'><td>'+pid+'</td>'+
+ '<td><div class="spark" id="spark-'+pid+'" /><span /></td><td></td><td></td><td></td>'+
+ '</tr>');
+ /* Create sparkline visualisation */
+ const spark = row.find('#spark-'+pid);
+ spark.css({'margin-right': '1em', width: '80px', height: '1.4em'});
+ workerData[pid] = {timestamp: timestamp, data: [[new Date(timestamp * 1000),0,0]], last: resp[pid]};
+ const workerGraph = new Dygraph(spark[0],
+ workerData[pid].data, {
+ valueRange: [0, 100],
+ legend: 'never',
+ axes : {
+ x : {
+ drawGrid: false,
+ drawAxis : false,
+ },
+ y : {
+ drawGrid: false,
+ drawAxis : false,
+ }
+ },
+ labels: ['x', '%user', '%sys'],
+ labelsDiv: '',
+ stackedGraph: true,
+ }
+ );
+ workerData[pid].graph = workerGraph;
+ }
+ updateWorker(row, resp[pid], workerData[pid], timestamp, buffer);
+ /* Track last datapoint */
+ workerData[pid].last = resp[pid];
+ workerData[pid].timestamp = timestamp;
+ }
+ /* Prune unhealthy PIDs */
+ if (!buffer) {
+ workerTable.find('tr').each(function () {
+ const e = $(this);
+ if (!(e.data('pid') in resp)) {
+ const healthCell = e.find('td').last();
+ healthCell.removeClass('text-success')
+ healthCell.text('Dead').addClass('text-danger');
+ }
+ });
+ }
+ }
+
+ /* WebSocket endpoints */
+ var wsStats = ('https:' == document.location.protocol ? 'wss://' : 'ws://') + location.host + '/stats';
+ var ws = new Socket(wsStats);
+ ws.onmessage = function(evt) {
+ var data = JSON.parse(evt.data);
+ if (data[0]) {
+ if (data.length > 0) {
+ pushUpstreams(data[data.length - 1].upstreams);
+ }
+ /* Buffer datapoints and redraw last */
+ for (var i in data) {
+ const is_last = (i == data.length - 1);
+ pushWorkers(data[i].workers, data[i].time, !is_last);
+ pushMetrics(data[i].stats, data[i].time, !is_last);
+ }
+ } else {
+ pushUpstreams(data.upstreams);
+ pushWorkers(data.workers, data.time);
+ pushMetrics(data.stats, data.time);
+ }
+ };
+
+ chartElement.addEventListener( 'mouseover', ( event ) =>
+ {
+ isGraphPaused = true;
+ }, false );
+
+ chartElement.addEventListener( 'mouseout', ( event ) =>
+ {
+ isGraphPaused = false;
+ }, false );
+
+});
diff --git a/modules/http/static/main.tpl b/modules/http/static/main.tpl
new file mode 100644
index 0000000..087f19e
--- /dev/null
+++ b/modules/http/static/main.tpl
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>{{ title }}</title>
+<script type="text/javascript">
+ var host = "{{ host }}";
+</script>
+<script src="jquery.js"></script>
+<script src="bootstrap.min.js"></script>
+<script src="d3.js"></script>
+<script src="dygraph.min.js"></script>
+<script src="selectize.min.js"></script>
+<script src="topojson.js"></script>
+<script src="datamaps.world.min.js"></script>
+<script src="kresd.js"></script>
+<link rel="icon" type="image/ico" href="favicon.ico">
+<link href="kresd.css" rel="stylesheet">
+<link href="bootstrap.min.css" rel="stylesheet">
+<link href="selectize.bootstrap3.css" rel="stylesheet">
+<nav class="navbar navbar-inverse navbar-fixed-top">
+ <div class="container">
+ <div class="navbar-header">
+ <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ <a class="navbar-brand" href="#">{{ title }}</a>
+ </div>
+ <ul class="nav navbar-nav navbar-right">
+ <li><a href="#">Metrics</a></li>
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Modules <span class="caret"></span></a>
+ <ul class="dropdown-menu" id="modules-dropdown">
+ </ul>
+ </li>
+ </ul>
+ </div>
+</nav>
+<div class="container">
+ <div class="main">
+ <h2 class="sub-header">Metrics</h2>
+ <div class="col-md-12">
+ <div class="row">
+ <div id="stats" class="row placeholders">
+ <div id="chart" style="width:100%"></div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-md-12">
+ <h3><small>More metrics</small></h3>
+ <div class="col-md-11">
+ <select id="chart-selector" multiple></select>
+ </div>
+ <div class="col-md-1">
+ <div class="checkbox">
+ <label><input id="chart-stacked" type="checkbox">Stacked</label>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <h3>Instances</h3>
+ <div class="col-md-12">
+ <table id="workers" class="table table-responsive">
+ <tr>
+ <th>PID</th><th>CPU per-worker (user/sys)</th>
+ <th>RSS</th><th>Page faults</th><th>Status</th>
+ </tr>
+ </table>
+ </div>
+ </div>
+ </div>
+ <div class="row" id="map-container">
+ <a name="worldmap"></a>
+ <h2 class="sub-header">Outbound queries</h2>
+ <div class="col-md-12">
+ <div id="map" style="position: relative;"></div>
+ </div>
+ </div>
+ <div class="col-md-12">
+ {{ snippets }}
+ </div>
+ </div>
+</div>
diff --git a/modules/http/static/selectize.bootstrap3.css b/modules/http/static/selectize.bootstrap3.css
new file mode 100644
index 0000000..abc02b6
--- /dev/null
+++ b/modules/http/static/selectize.bootstrap3.css
@@ -0,0 +1,418 @@
+/**
+ * selectize.bootstrap3.css (v0.12.6) - Bootstrap 3 Theme
+ * Copyright (c) 2013–2015 Brian Reavis & contributors
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
+ * file except in compliance with the License. You may obtain a copy of the License at:
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
+ * ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ *
+ * @author Brian Reavis <brian@thirdroute.com>
+ */
+.selectize-control.plugin-drag_drop.multi > .selectize-input > div.ui-sortable-placeholder {
+ visibility: visible !important;
+ background: #f2f2f2 !important;
+ background: rgba(0, 0, 0, 0.06) !important;
+ border: 0 none !important;
+ -webkit-box-shadow: inset 0 0 12px 4px #fff;
+ box-shadow: inset 0 0 12px 4px #fff;
+}
+.selectize-control.plugin-drag_drop .ui-sortable-placeholder::after {
+ content: '!';
+ visibility: hidden;
+}
+.selectize-control.plugin-drag_drop .ui-sortable-helper {
+ -webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+}
+.selectize-dropdown-header {
+ position: relative;
+ padding: 3px 12px;
+ border-bottom: 1px solid #d0d0d0;
+ background: #f8f8f8;
+ -webkit-border-radius: 4px 4px 0 0;
+ -moz-border-radius: 4px 4px 0 0;
+ border-radius: 4px 4px 0 0;
+}
+.selectize-dropdown-header-close {
+ position: absolute;
+ right: 12px;
+ top: 50%;
+ color: #333333;
+ opacity: 0.4;
+ margin-top: -12px;
+ line-height: 20px;
+ font-size: 20px !important;
+}
+.selectize-dropdown-header-close:hover {
+ color: #000000;
+}
+.selectize-dropdown.plugin-optgroup_columns .optgroup {
+ border-right: 1px solid #f2f2f2;
+ border-top: 0 none;
+ float: left;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.selectize-dropdown.plugin-optgroup_columns .optgroup:last-child {
+ border-right: 0 none;
+}
+.selectize-dropdown.plugin-optgroup_columns .optgroup:before {
+ display: none;
+}
+.selectize-dropdown.plugin-optgroup_columns .optgroup-header {
+ border-top: 0 none;
+}
+.selectize-control.plugin-remove_button [data-value] {
+ position: relative;
+ padding-right: 24px !important;
+}
+.selectize-control.plugin-remove_button [data-value] .remove {
+ z-index: 1;
+ /* fixes ie bug (see #392) */
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ width: 17px;
+ text-align: center;
+ font-weight: bold;
+ font-size: 12px;
+ color: inherit;
+ text-decoration: none;
+ vertical-align: middle;
+ display: inline-block;
+ padding: 1px 0 0 0;
+ border-left: 1px solid rgba(0, 0, 0, 0);
+ -webkit-border-radius: 0 2px 2px 0;
+ -moz-border-radius: 0 2px 2px 0;
+ border-radius: 0 2px 2px 0;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.selectize-control.plugin-remove_button [data-value] .remove:hover {
+ background: rgba(0, 0, 0, 0.05);
+}
+.selectize-control.plugin-remove_button [data-value].active .remove {
+ border-left-color: rgba(0, 0, 0, 0);
+}
+.selectize-control.plugin-remove_button .disabled [data-value] .remove:hover {
+ background: none;
+}
+.selectize-control.plugin-remove_button .disabled [data-value] .remove {
+ border-left-color: rgba(77, 77, 77, 0);
+}
+.selectize-control.plugin-remove_button .remove-single {
+ position: absolute;
+ right: 0;
+ top: 0;
+ font-size: 23px;
+}
+.selectize-control {
+ position: relative;
+}
+.selectize-dropdown,
+.selectize-input,
+.selectize-input input {
+ color: #333333;
+ font-family: inherit;
+ font-size: inherit;
+ line-height: 20px;
+ -webkit-font-smoothing: inherit;
+}
+.selectize-input,
+.selectize-control.single .selectize-input.input-active {
+ background: #fff;
+ cursor: text;
+ display: inline-block;
+}
+.selectize-input {
+ border: 1px solid #ccc;
+ padding: 6px 12px;
+ display: inline-block;
+ width: 100%;
+ overflow: hidden;
+ position: relative;
+ z-index: 1;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+.selectize-control.multi .selectize-input.has-items {
+ padding: 5px 12px 2px;
+}
+.selectize-input.full {
+ background-color: #fff;
+}
+.selectize-input.disabled,
+.selectize-input.disabled * {
+ cursor: default !important;
+}
+.selectize-input.focus {
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15);
+}
+.selectize-input.dropdown-active {
+ -webkit-border-radius: 4px 4px 0 0;
+ -moz-border-radius: 4px 4px 0 0;
+ border-radius: 4px 4px 0 0;
+}
+.selectize-input > * {
+ vertical-align: baseline;
+ display: -moz-inline-stack;
+ display: inline-block;
+ zoom: 1;
+ *display: inline;
+}
+.selectize-control.multi .selectize-input > div {
+ cursor: pointer;
+ margin: 0 3px 3px 0;
+ padding: 1px 3px;
+ background: #efefef;
+ color: #333333;
+ border: 0 solid rgba(0, 0, 0, 0);
+}
+.selectize-control.multi .selectize-input > div.active {
+ background: #428bca;
+ color: #fff;
+ border: 0 solid rgba(0, 0, 0, 0);
+}
+.selectize-control.multi .selectize-input.disabled > div,
+.selectize-control.multi .selectize-input.disabled > div.active {
+ color: #808080;
+ background: #ffffff;
+ border: 0 solid rgba(77, 77, 77, 0);
+}
+.selectize-input > input {
+ display: inline-block !important;
+ padding: 0 !important;
+ min-height: 0 !important;
+ max-height: none !important;
+ max-width: 100% !important;
+ margin: 0 !important;
+ text-indent: 0 !important;
+ border: 0 none !important;
+ background: none !important;
+ line-height: inherit !important;
+ -webkit-user-select: auto !important;
+ -webkit-box-shadow: none !important;
+ box-shadow: none !important;
+}
+.selectize-input > input::-ms-clear {
+ display: none;
+}
+.selectize-input > input:focus {
+ outline: none !important;
+}
+.selectize-input::after {
+ content: ' ';
+ display: block;
+ clear: left;
+}
+.selectize-input.dropdown-active::before {
+ content: ' ';
+ display: block;
+ position: absolute;
+ background: #ffffff;
+ height: 1px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+}
+.selectize-dropdown {
+ position: absolute;
+ z-index: 10;
+ border: 1px solid #d0d0d0;
+ background: #fff;
+ margin: -1px 0 0 0;
+ border-top: 0 none;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ -webkit-border-radius: 0 0 4px 4px;
+ -moz-border-radius: 0 0 4px 4px;
+ border-radius: 0 0 4px 4px;
+}
+.selectize-dropdown [data-selectable] {
+ cursor: pointer;
+ overflow: hidden;
+}
+.selectize-dropdown [data-selectable] .highlight {
+ background: rgba(255, 237, 40, 0.4);
+ -webkit-border-radius: 1px;
+ -moz-border-radius: 1px;
+ border-radius: 1px;
+}
+.selectize-dropdown .option,
+.selectize-dropdown .optgroup-header {
+ padding: 3px 12px;
+}
+.selectize-dropdown .option,
+.selectize-dropdown [data-disabled],
+.selectize-dropdown [data-disabled] [data-selectable].option {
+ cursor: inherit;
+ opacity: 0.5;
+}
+.selectize-dropdown [data-selectable].option {
+ opacity: 1;
+}
+.selectize-dropdown .optgroup:first-child .optgroup-header {
+ border-top: 0 none;
+}
+.selectize-dropdown .optgroup-header {
+ color: #777777;
+ background: #fff;
+ cursor: default;
+}
+.selectize-dropdown .active {
+ background-color: #f5f5f5;
+ color: #262626;
+}
+.selectize-dropdown .active.create {
+ color: #262626;
+}
+.selectize-dropdown .create {
+ color: rgba(51, 51, 51, 0.5);
+}
+.selectize-dropdown-content {
+ overflow-y: auto;
+ overflow-x: hidden;
+ max-height: 200px;
+ -webkit-overflow-scrolling: touch;
+}
+.selectize-control.single .selectize-input,
+.selectize-control.single .selectize-input input {
+ cursor: pointer;
+}
+.selectize-control.single .selectize-input.input-active,
+.selectize-control.single .selectize-input.input-active input {
+ cursor: text;
+}
+.selectize-control.single .selectize-input:after {
+ content: ' ';
+ display: block;
+ position: absolute;
+ top: 50%;
+ right: 17px;
+ margin-top: -3px;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 5px 5px 0 5px;
+ border-color: #333333 transparent transparent transparent;
+}
+.selectize-control.single .selectize-input.dropdown-active:after {
+ margin-top: -4px;
+ border-width: 0 5px 5px 5px;
+ border-color: transparent transparent #333333 transparent;
+}
+.selectize-control.rtl.single .selectize-input:after {
+ left: 17px;
+ right: auto;
+}
+.selectize-control.rtl .selectize-input > input {
+ margin: 0 4px 0 -2px !important;
+}
+.selectize-control .selectize-input.disabled {
+ opacity: 0.5;
+ background-color: #fff;
+}
+.selectize-dropdown,
+.selectize-dropdown.form-control {
+ height: auto;
+ padding: 0;
+ margin: 2px 0 0 0;
+ z-index: 1000;
+ background: #fff;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+}
+.selectize-dropdown .optgroup-header {
+ font-size: 12px;
+ line-height: 1.42857143;
+}
+.selectize-dropdown .optgroup:first-child:before {
+ display: none;
+}
+.selectize-dropdown .optgroup:before {
+ content: ' ';
+ display: block;
+ height: 1px;
+ margin: 9px 0;
+ overflow: hidden;
+ background-color: #e5e5e5;
+ margin-left: -12px;
+ margin-right: -12px;
+}
+.selectize-dropdown-content {
+ padding: 5px 0;
+}
+.selectize-dropdown-header {
+ padding: 6px 12px;
+}
+.selectize-input {
+ min-height: 34px;
+}
+.selectize-input.dropdown-active {
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+.selectize-input.dropdown-active::before {
+ display: none;
+}
+.selectize-input.focus {
+ border-color: #66afe9;
+ outline: 0;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
+}
+.has-error .selectize-input {
+ border-color: #a94442;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+.has-error .selectize-input:focus {
+ border-color: #843534;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
+}
+.selectize-control.multi .selectize-input.has-items {
+ padding-left: 9px;
+ padding-right: 9px;
+}
+.selectize-control.multi .selectize-input > div {
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+.form-control.selectize-control {
+ padding: 0;
+ height: auto;
+ border: none;
+ background: none;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
diff --git a/modules/http/static/selectize.min.js b/modules/http/static/selectize.min.js
new file mode 100644
index 0000000..6161cd7
--- /dev/null
+++ b/modules/http/static/selectize.min.js
@@ -0,0 +1,4 @@
+/*! selectize.js - v0.12.6 | https://github.com/selectize/selectize.js | SPDX-License-Identifier: Apache-2.0 */
+
+!function(a,b){"function"==typeof define&&define.amd?define("sifter",b):"object"==typeof exports?module.exports=b():a.Sifter=b()}(this,function(){var a=function(a,b){this.items=a,this.settings=b||{diacritics:!0}};a.prototype.tokenize=function(a){if(!(a=e(String(a||"").toLowerCase()))||!a.length)return[];var b,c,d,g,i=[],j=a.split(/ +/);for(b=0,c=j.length;b<c;b++){if(d=f(j[b]),this.settings.diacritics)for(g in h)h.hasOwnProperty(g)&&(d=d.replace(new RegExp(g,"g"),h[g]));i.push({string:j[b],regex:new RegExp(d,"i")})}return i},a.prototype.iterator=function(a,b){var c;c=g(a)?Array.prototype.forEach||function(a){for(var b=0,c=this.length;b<c;b++)a(this[b],b,this)}:function(a){for(var b in this)this.hasOwnProperty(b)&&a(this[b],b,this)},c.apply(a,[b])},a.prototype.getScoreFunction=function(a,b){var c,e,f,g,h;c=this,a=c.prepareSearch(a,b),f=a.tokens,e=a.options.fields,g=f.length,h=a.options.nesting;var i=function(a,b){var c,d;return a?(a=String(a||""),-1===(d=a.search(b.regex))?0:(c=b.string.length/a.length,0===d&&(c+=.5),c)):0},j=function(){var a=e.length;return a?1===a?function(a,b){return i(d(b,e[0],h),a)}:function(b,c){for(var f=0,g=0;f<a;f++)g+=i(d(c,e[f],h),b);return g/a}:function(){return 0}}();return g?1===g?function(a){return j(f[0],a)}:"and"===a.options.conjunction?function(a){for(var b,c=0,d=0;c<g;c++){if((b=j(f[c],a))<=0)return 0;d+=b}return d/g}:function(a){for(var b=0,c=0;b<g;b++)c+=j(f[b],a);return c/g}:function(){return 0}},a.prototype.getSortFunction=function(a,c){var e,f,g,h,i,j,k,l,m,n,o;if(g=this,a=g.prepareSearch(a,c),o=!a.query&&c.sort_empty||c.sort,m=function(a,b){return"$score"===a?b.score:d(g.items[b.id],a,c.nesting)},i=[],o)for(e=0,f=o.length;e<f;e++)(a.query||"$score"!==o[e].field)&&i.push(o[e]);if(a.query){for(n=!0,e=0,f=i.length;e<f;e++)if("$score"===i[e].field){n=!1;break}n&&i.unshift({field:"$score",direction:"desc"})}else for(e=0,f=i.length;e<f;e++)if("$score"===i[e].field){i.splice(e,1);break}for(l=[],e=0,f=i.length;e<f;e++)l.push("desc"===i[e].direction?-1:1);return j=i.length,j?1===j?(h=i[0].field,k=l[0],function(a,c){return k*b(m(h,a),m(h,c))}):function(a,c){var d,e,f;for(d=0;d<j;d++)if(f=i[d].field,e=l[d]*b(m(f,a),m(f,c)))return e;return 0}:null},a.prototype.prepareSearch=function(a,b){if("object"==typeof a)return a;b=c({},b);var d=b.fields,e=b.sort,f=b.sort_empty;return d&&!g(d)&&(b.fields=[d]),e&&!g(e)&&(b.sort=[e]),f&&!g(f)&&(b.sort_empty=[f]),{options:b,query:String(a||"").toLowerCase(),tokens:this.tokenize(a),total:0,items:[]}},a.prototype.search=function(a,b){var c,d,e,f,g=this;return d=this.prepareSearch(a,b),b=d.options,a=d.query,f=b.score||g.getScoreFunction(d),a.length?g.iterator(g.items,function(a,e){c=f(a),(!1===b.filter||c>0)&&d.items.push({score:c,id:e})}):g.iterator(g.items,function(a,b){d.items.push({score:1,id:b})}),e=g.getSortFunction(d,b),e&&d.items.sort(e),d.total=d.items.length,"number"==typeof b.limit&&(d.items=d.items.slice(0,b.limit)),d};var b=function(a,b){return"number"==typeof a&&"number"==typeof b?a>b?1:a<b?-1:0:(a=i(String(a||"")),b=i(String(b||"")),a>b?1:b>a?-1:0)},c=function(a,b){var c,d,e,f;for(c=1,d=arguments.length;c<d;c++)if(f=arguments[c])for(e in f)f.hasOwnProperty(e)&&(a[e]=f[e]);return a},d=function(a,b,c){if(a&&b){if(!c)return a[b];for(var d=b.split(".");d.length&&(a=a[d.shift()]););return a}},e=function(a){return(a+"").replace(/^\s+|\s+$|/g,"")},f=function(a){return(a+"").replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")},g=Array.isArray||"undefined"!=typeof $&&$.isArray||function(a){return"[object Array]"===Object.prototype.toString.call(a)},h={a:"[aḀá¸Ä‚ăÂâÇǎȺⱥȦȧẠạÄäÀàÃáĀÄÃãÅåąĄÃąĄ]",b:"[bâ¢Î²Î’B฿ðŒá›’]",c:"[cĆćĈĉČÄÄŠÄ‹CÌ„c̄ÇçḈḉȻȼƇƈɕᴄCc]",d:"[dÄŽÄḊḋá¸á¸‘Ḍá¸á¸’ḓḎá¸ÄÄ‘D̦d̦ƉɖƊɗƋƌᵭá¶á¶‘ȡᴅDdð]",e:"[eÉéÈèÊêḘḙĚěĔĕẼẽḚḛẺẻĖėËëĒēȨȩĘęᶒɆɇȄȅẾếỀá»á»„ễỂểḜá¸á¸–ḗḔḕȆȇẸẹỆệⱸᴇEeɘÇÆÆε]",f:"[fƑƒḞḟ]",g:"[gɢ₲ǤǥĜÄĞğĢģƓɠĠġ]",h:"[hĤĥĦħḨḩẖẖḤḥḢḣɦʰǶƕ]",i:"[iÃíÌìĬĭÎîÇÇÃïḮḯĨĩĮįĪīỈỉȈȉȊȋỊịḬḭƗɨɨ̆ᵻᶖİiIıɪIi]",j:"[jȷĴĵɈɉÊɟʲ]",k:"[kƘƙê€êḰḱǨǩḲḳḴḵκϰ₭]",l:"[lÅłĽľĻļĹĺḶḷḸḹḼḽḺḻĿŀȽƚⱠⱡⱢɫɬᶅɭȴʟLl]",n:"[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ÆɲȠƞᵰᶇɳȵɴNnŊŋ]",o:"[oØøÖöÓóÒòÔôǑǒÅÅ‘ÅŽÅȮȯỌá»ÆŸÉµÆ Æ¡á»Žá»ÅŒÅÕõǪǫȌÈÕ•Ö…]",p:"[pṔṕṖṗⱣᵽƤƥᵱ]",q:"[qê–ê—Ê ÉŠÉ‹ê˜ê™q̃]",r:"[rŔŕɌÉŘřŖŗṘṙÈȑȒȓṚṛⱤɽ]",s:"[sŚśṠṡṢṣꞨꞩŜÅŠšŞşȘșS̈s̈]",t:"[tŤťṪṫŢţṬṭƮʈȚțṰṱṮṯƬƭ]",u:"[uŬŭɄʉỤụÜüÚúÙùÛûǓǔŰűŬŭƯưỦủŪūŨũŲųȔȕ∪]",v:"[vṼṽṾṿƲʋêžêŸâ±±Ê‹]",w:"[wẂẃẀáºÅ´Åµáº„ẅẆẇẈẉ]",x:"[xẌáºáºŠáº‹Ï‡]",y:"[yÃýỲỳŶŷŸÿỸỹẎáºá»´á»µÉŽÉƳƴ]",z:"[zŹźáºáº‘ŽžŻżẒẓẔẕƵƶ]"},i=function(){var a,b,c,d,e="",f={};for(c in h)if(h.hasOwnProperty(c))for(d=h[c].substring(2,h[c].length-1),e+=d,a=0,b=d.length;a<b;a++)f[d.charAt(a)]=c;var g=new RegExp("["+e+"]","g");return function(a){return a.replace(g,function(a){return f[a]}).toLowerCase()}}();return a}),function(a,b){"function"==typeof define&&define.amd?define("microplugin",b):"object"==typeof exports?module.exports=b():a.MicroPlugin=b()}(this,function(){var a={};a.mixin=function(a){a.plugins={},a.prototype.initializePlugins=function(a){var c,d,e,f=this,g=[];if(f.plugins={names:[],settings:{},requested:{},loaded:{}},b.isArray(a))for(c=0,d=a.length;c<d;c++)"string"==typeof a[c]?g.push(a[c]):(f.plugins.settings[a[c].name]=a[c].options,g.push(a[c].name));else if(a)for(e in a)a.hasOwnProperty(e)&&(f.plugins.settings[e]=a[e],g.push(e));for(;g.length;)f.require(g.shift())},a.prototype.loadPlugin=function(b){var c=this,d=c.plugins,e=a.plugins[b];if(!a.plugins.hasOwnProperty(b))throw new Error('Unable to find "'+b+'" plugin');d.requested[b]=!0,d.loaded[b]=e.fn.apply(c,[c.plugins.settings[b]||{}]),d.names.push(b)},a.prototype.require=function(a){var b=this,c=b.plugins;if(!b.plugins.loaded.hasOwnProperty(a)){if(c.requested[a])throw new Error('Plugin has circular dependency ("'+a+'")');b.loadPlugin(a)}return c.loaded[a]},a.define=function(b,c){a.plugins[b]={name:b,fn:c}}};var b={isArray:Array.isArray||function(a){return"[object Array]"===Object.prototype.toString.call(a)}};return a}),function(a,b){"function"==typeof define&&define.amd?define("selectize",["jquery","sifter","microplugin"],b):"object"==typeof exports?module.exports=b(require("jquery"),require("sifter"),require("microplugin")):a.Selectize=b(a.jQuery,a.Sifter,a.MicroPlugin)}(this,function(a,b,c){"use strict";var d=function(a,b){if("string"!=typeof b||b.length){var c="string"==typeof b?new RegExp(b,"i"):b,d=function(a){var b=0;if(3===a.nodeType){var e=a.data.search(c);if(e>=0&&a.data.length>0){var f=a.data.match(c),g=document.createElement("span");g.className="highlight";var h=a.splitText(e),i=(h.splitText(f[0].length),h.cloneNode(!0));g.appendChild(i),h.parentNode.replaceChild(g,h),b=1}}else if(1===a.nodeType&&a.childNodes&&!/(script|style)/i.test(a.tagName)&&("highlight"!==a.className||"SPAN"!==a.tagName))for(var j=0;j<a.childNodes.length;++j)j+=d(a.childNodes[j]);return b};return a.each(function(){d(this)})}};a.fn.removeHighlight=function(){return this.find("span.highlight").each(function(){this.parentNode.firstChild.nodeName;var a=this.parentNode;a.replaceChild(this.firstChild,this),a.normalize()}).end()};var e=function(){};e.prototype={on:function(a,b){this._events=this._events||{},this._events[a]=this._events[a]||[],this._events[a].push(b)},off:function(a,b){var c=arguments.length;return 0===c?delete this._events:1===c?delete this._events[a]:(this._events=this._events||{},void(a in this._events!=!1&&this._events[a].splice(this._events[a].indexOf(b),1)))},trigger:function(a){if(this._events=this._events||{},a in this._events!=!1)for(var b=0;b<this._events[a].length;b++)this._events[a][b].apply(this,Array.prototype.slice.call(arguments,1))}},e.mixin=function(a){for(var b=["on","off","trigger"],c=0;c<b.length;c++)a.prototype[b[c]]=e.prototype[b[c]]};var f=/Mac/.test(navigator.userAgent),g=f?91:17,h=f?18:17,i=!/android/i.test(window.navigator.userAgent)&&!!document.createElement("input").validity,j=function(a){return void 0!==a},k=function(a){return void 0===a||null===a?null:"boolean"==typeof a?a?"1":"0":a+""},l=function(a){return(a+"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},m={};m.before=function(a,b,c){var d=a[b];a[b]=function(){return c.apply(a,arguments),d.apply(a,arguments)}},m.after=function(a,b,c){var d=a[b];a[b]=function(){var b=d.apply(a,arguments);return c.apply(a,arguments),b}};var n=function(a){var b=!1;return function(){b||(b=!0,a.apply(this,arguments))}},o=function(a,b){var c;return function(){var d=this,e=arguments;window.clearTimeout(c),c=window.setTimeout(function(){a.apply(d,e)},b)}},p=function(a,b,c){var d,e=a.trigger,f={};a.trigger=function(){var c=arguments[0];if(-1===b.indexOf(c))return e.apply(a,arguments);f[c]=arguments},c.apply(a,[]),a.trigger=e;for(d in f)f.hasOwnProperty(d)&&e.apply(a,f[d])},q=function(a,b,c,d){a.on(b,c,function(b){for(var c=b.target;c&&c.parentNode!==a[0];)c=c.parentNode;return b.currentTarget=c,d.apply(this,[b])})},r=function(a){var b={};if("selectionStart"in a)b.start=a.selectionStart,b.length=a.selectionEnd-b.start;else if(document.selection){a.focus();var c=document.selection.createRange(),d=document.selection.createRange().text.length;c.moveStart("character",-a.value.length),b.start=c.text.length-d,b.length=d}return b},s=function(a,b,c){var d,e,f={};if(c)for(d=0,e=c.length;d<e;d++)f[c[d]]=a.css(c[d]);else f=a.css();b.css(f)},t=function(b,c){return b?(w.$testInput||(w.$testInput=a("<span />").css({position:"absolute",top:-99999,left:-99999,width:"auto",padding:0,whiteSpace:"pre"}).appendTo("body")),w.$testInput.text(b),s(c,w.$testInput,["letterSpacing","fontSize","fontFamily","fontWeight","textTransform"]),w.$testInput.width()):0},u=function(a){var b=null,c=function(c,d){var e,f,g,h,i,j,k,l;c=c||window.event||{},d=d||{},c.metaKey||c.altKey||(d.force||!1!==a.data("grow"))&&(e=a.val(),c.type&&"keydown"===c.type.toLowerCase()&&(f=c.keyCode,g=f>=48&&f<=57||f>=65&&f<=90||f>=96&&f<=111||f>=186&&f<=222||32===f,46===f||8===f?(l=r(a[0]),l.length?e=e.substring(0,l.start)+e.substring(l.start+l.length):8===f&&l.start?e=e.substring(0,l.start-1)+e.substring(l.start+1):46===f&&void 0!==l.start&&(e=e.substring(0,l.start)+e.substring(l.start+1))):g&&(j=c.shiftKey,k=String.fromCharCode(c.keyCode),k=j?k.toUpperCase():k.toLowerCase(),e+=k)),h=a.attr("placeholder"),!e&&h&&(e=h),(i=t(e,a)+4)!==b&&(b=i,a.width(i),a.triggerHandler("resize")))};a.on("keydown keyup update blur",c),c()},v=function(a){var b=document.createElement("div");return b.appendChild(a.cloneNode(!0)),b.innerHTML},w=function(c,d){var e,f,g,h,i=this;h=c[0],h.selectize=i;var j=window.getComputedStyle&&window.getComputedStyle(h,null);if(g=j?j.getPropertyValue("direction"):h.currentStyle&&h.currentStyle.direction,g=g||c.parents("[dir]:first").attr("dir")||"",a.extend(i,{order:0,settings:d,$input:c,tabIndex:c.attr("tabindex")||"",tagType:"select"===h.tagName.toLowerCase()?1:2,rtl:/rtl/i.test(g),eventNS:".selectize"+ ++w.count,highlightedValue:null,isBlurring:!1,isOpen:!1,isDisabled:!1,isRequired:c.is("[required]"),isInvalid:!1,isLocked:!1,isFocused:!1,isInputHidden:!1,isSetup:!1,isShiftDown:!1,isCmdDown:!1,isCtrlDown:!1,ignoreFocus:!1,ignoreBlur:!1,ignoreHover:!1,hasOptions:!1,currentResults:null,lastValue:"",caretPos:0,loading:0,loadedSearches:{},$activeOption:null,$activeItems:[],optgroups:{},options:{},userOptions:{},items:[],renderCache:{},onSearchChange:null===d.loadThrottle?i.onSearchChange:o(i.onSearchChange,d.loadThrottle)}),i.sifter=new b(this.options,{diacritics:d.diacritics}),i.settings.options){for(e=0,f=i.settings.options.length;e<f;e++)i.registerOption(i.settings.options[e]);delete i.settings.options}if(i.settings.optgroups){for(e=0,f=i.settings.optgroups.length;e<f;e++)i.registerOptionGroup(i.settings.optgroups[e]);delete i.settings.optgroups}i.settings.mode=i.settings.mode||(1===i.settings.maxItems?"single":"multi"),"boolean"!=typeof i.settings.hideSelected&&(i.settings.hideSelected="multi"===i.settings.mode),i.initializePlugins(i.settings.plugins),i.setupCallbacks(),i.setupTemplates(),i.setup()};return e.mixin(w),void 0!==c?c.mixin(w):function(a,b){b||(b={});console.error("Selectize: "+a),b.explanation&&(console.group&&console.group(),console.error(b.explanation),console.group&&console.groupEnd())}("Dependency MicroPlugin is missing",{explanation:'Make sure you either: (1) are using the "standalone" version of Selectize, or (2) require MicroPlugin before you load Selectize.'}),a.extend(w.prototype,{setup:function(){var b,c,d,e,j,k,l,m,n,o,p=this,r=p.settings,s=p.eventNS,t=a(window),v=a(document),w=p.$input;if(l=p.settings.mode,m=w.attr("class")||"",b=a("<div>").addClass(r.wrapperClass).addClass(m).addClass(l),c=a("<div>").addClass(r.inputClass).addClass("items").appendTo(b),d=a('<input type="text" autocomplete="off" />').appendTo(c).attr("tabindex",w.is(":disabled")?"-1":p.tabIndex),k=a(r.dropdownParent||b),e=a("<div>").addClass(r.dropdownClass).addClass(l).hide().appendTo(k),j=a("<div>").addClass(r.dropdownContentClass).appendTo(e),(o=w.attr("id"))&&(d.attr("id",o+"-selectized"),a("label[for='"+o+"']").attr("for",o+"-selectized")),p.settings.copyClassesToDropdown&&e.addClass(m),b.css({width:w[0].style.width}),p.plugins.names.length&&(n="plugin-"+p.plugins.names.join(" plugin-"),b.addClass(n),e.addClass(n)),(null===r.maxItems||r.maxItems>1)&&1===p.tagType&&w.attr("multiple","multiple"),p.settings.placeholder&&d.attr("placeholder",r.placeholder),!p.settings.splitOn&&p.settings.delimiter){var x=p.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&");p.settings.splitOn=new RegExp("\\s*"+x+"+\\s*")}w.attr("autocorrect")&&d.attr("autocorrect",w.attr("autocorrect")),w.attr("autocapitalize")&&d.attr("autocapitalize",w.attr("autocapitalize")),d[0].type=w[0].type,p.$wrapper=b,p.$control=c,p.$control_input=d,p.$dropdown=e,p.$dropdown_content=j,e.on("mouseenter mousedown click","[data-disabled]>[data-selectable]",function(a){a.stopImmediatePropagation()}),e.on("mouseenter","[data-selectable]",function(){return p.onOptionHover.apply(p,arguments)}),e.on("mousedown click","[data-selectable]",function(){return p.onOptionSelect.apply(p,arguments)}),q(c,"mousedown","*:not(input)",function(){return p.onItemSelect.apply(p,arguments)}),u(d),c.on({mousedown:function(){return p.onMouseDown.apply(p,arguments)},click:function(){return p.onClick.apply(p,arguments)}}),d.on({mousedown:function(a){a.stopPropagation()},keydown:function(){return p.onKeyDown.apply(p,arguments)},keyup:function(){return p.onKeyUp.apply(p,arguments)},keypress:function(){return p.onKeyPress.apply(p,arguments)},resize:function(){p.positionDropdown.apply(p,[])},blur:function(){return p.onBlur.apply(p,arguments)},focus:function(){return p.ignoreBlur=!1,p.onFocus.apply(p,arguments)},paste:function(){return p.onPaste.apply(p,arguments)}}),v.on("keydown"+s,function(a){p.isCmdDown=a[f?"metaKey":"ctrlKey"],p.isCtrlDown=a[f?"altKey":"ctrlKey"],p.isShiftDown=a.shiftKey}),v.on("keyup"+s,function(a){a.keyCode===h&&(p.isCtrlDown=!1),16===a.keyCode&&(p.isShiftDown=!1),a.keyCode===g&&(p.isCmdDown=!1)}),v.on("mousedown"+s,function(a){if(p.isFocused){if(a.target===p.$dropdown[0]||a.target.parentNode===p.$dropdown[0])return!1;p.$control.has(a.target).length||a.target===p.$control[0]||p.blur(a.target)}}),t.on(["scroll"+s,"resize"+s].join(" "),function(){p.isOpen&&p.positionDropdown.apply(p,arguments)}),t.on("mousemove"+s,function(){p.ignoreHover=!1}),this.revertSettings={$children:w.children().detach(),tabindex:w.attr("tabindex")},w.attr("tabindex",-1).hide().after(p.$wrapper),a.isArray(r.items)&&(p.setValue(r.items),delete r.items),i&&w.on("invalid"+s,function(a){a.preventDefault(),p.isInvalid=!0,p.refreshState()}),p.updateOriginalInput(),p.refreshItems(),p.refreshState(),p.updatePlaceholder(),p.isSetup=!0,w.is(":disabled")&&p.disable(),p.on("change",this.onChange),w.data("selectize",p),w.addClass("selectized"),p.trigger("initialize"),!0===r.preload&&p.onSearchChange("")},setupTemplates:function(){var b=this,c=b.settings.labelField,d=b.settings.optgroupLabelField,e={optgroup:function(a){return'<div class="optgroup">'+a.html+"</div>"},optgroup_header:function(a,b){return'<div class="optgroup-header">'+b(a[d])+"</div>"},option:function(a,b){return'<div class="option">'+b(a[c])+"</div>"},item:function(a,b){return'<div class="item">'+b(a[c])+"</div>"},option_create:function(a,b){return'<div class="create">Add <strong>'+b(a.input)+"</strong>&hellip;</div>"}};b.settings.render=a.extend({},e,b.settings.render)},setupCallbacks:function(){var a,b,c={initialize:"onInitialize",change:"onChange",item_add:"onItemAdd",item_remove:"onItemRemove",clear:"onClear",option_add:"onOptionAdd",option_remove:"onOptionRemove",option_clear:"onOptionClear",optgroup_add:"onOptionGroupAdd",optgroup_remove:"onOptionGroupRemove",optgroup_clear:"onOptionGroupClear",dropdown_open:"onDropdownOpen",dropdown_close:"onDropdownClose",type:"onType",load:"onLoad",focus:"onFocus",blur:"onBlur"};for(a in c)c.hasOwnProperty(a)&&(b=this.settings[c[a]])&&this.on(a,b)},onClick:function(a){var b=this;b.isFocused&&b.isOpen||(b.focus(),a.preventDefault())},onMouseDown:function(b){var c=this,d=b.isDefaultPrevented();a(b.target);if(c.isFocused){if(b.target!==c.$control_input[0])return"single"===c.settings.mode?c.isOpen?c.close():c.open():d||c.setActiveItem(null),!1}else d||window.setTimeout(function(){c.focus()},0)},onChange:function(){this.$input.trigger("change")},onPaste:function(b){var c=this;if(c.isFull()||c.isInputHidden||c.isLocked)return void b.preventDefault();c.settings.splitOn&&setTimeout(function(){var b=c.$control_input.val();if(b.match(c.settings.splitOn))for(var d=a.trim(b).split(c.settings.splitOn),e=0,f=d.length;e<f;e++)c.createItem(d[e])},0)},onKeyPress:function(a){if(this.isLocked)return a&&a.preventDefault();var b=String.fromCharCode(a.keyCode||a.which);return this.settings.create&&"multi"===this.settings.mode&&b===this.settings.delimiter?(this.createItem(),a.preventDefault(),!1):void 0},onKeyDown:function(a){var b=(a.target,this.$control_input[0],this);if(b.isLocked)return void(9!==a.keyCode&&a.preventDefault());switch(a.keyCode){case 65:if(b.isCmdDown)return void b.selectAll();break;case 27:return void(b.isOpen&&(a.preventDefault(),a.stopPropagation(),b.close()));case 78:if(!a.ctrlKey||a.altKey)break;case 40:if(!b.isOpen&&b.hasOptions)b.open();else if(b.$activeOption){b.ignoreHover=!0;var c=b.getAdjacentOption(b.$activeOption,1);c.length&&b.setActiveOption(c,!0,!0)}return void a.preventDefault();case 80:if(!a.ctrlKey||a.altKey)break;case 38:if(b.$activeOption){b.ignoreHover=!0;var d=b.getAdjacentOption(b.$activeOption,-1);d.length&&b.setActiveOption(d,!0,!0)}return void a.preventDefault();case 13:return void(b.isOpen&&b.$activeOption&&(b.onOptionSelect({currentTarget:b.$activeOption}),a.preventDefault()));case 37:return void b.advanceSelection(-1,a);case 39:return void b.advanceSelection(1,a);case 9:return b.settings.selectOnTab&&b.isOpen&&b.$activeOption&&(b.onOptionSelect({currentTarget:b.$activeOption}),b.isFull()||a.preventDefault()),void(b.settings.create&&b.createItem()&&a.preventDefault());case 8:case 46:return void b.deleteSelection(a)}return!b.isFull()&&!b.isInputHidden||(f?a.metaKey:a.ctrlKey)?void 0:void a.preventDefault()},onKeyUp:function(a){var b=this;if(b.isLocked)return a&&a.preventDefault();var c=b.$control_input.val()||"";b.lastValue!==c&&(b.lastValue=c,b.onSearchChange(c),b.refreshOptions(),b.trigger("type",c))},onSearchChange:function(a){var b=this,c=b.settings.load;c&&(b.loadedSearches.hasOwnProperty(a)||(b.loadedSearches[a]=!0,b.load(function(d){c.apply(b,[a,d])})))},onFocus:function(a){var b=this,c=b.isFocused;if(b.isDisabled)return b.blur(),a&&a.preventDefault(),!1;b.ignoreFocus||(b.isFocused=!0,"focus"===b.settings.preload&&b.onSearchChange(""),c||b.trigger("focus"),b.$activeItems.length||(b.showInput(),b.setActiveItem(null),b.refreshOptions(!!b.settings.openOnFocus)),b.refreshState())},onBlur:function(a,b){var c=this;if(c.isFocused&&(c.isFocused=!1,!c.ignoreFocus)){if(!c.ignoreBlur&&document.activeElement===c.$dropdown_content[0])return c.ignoreBlur=!0,void c.onFocus(a);var d=function(){c.close(),c.setTextboxValue(""),c.setActiveItem(null),c.setActiveOption(null),c.setCaret(c.items.length),c.refreshState(),b&&b.focus&&b.focus(),c.isBlurring=!1,c.ignoreFocus=!1,c.trigger("blur")};c.isBlurring=!0,c.ignoreFocus=!0,c.settings.create&&c.settings.createOnBlur?c.createItem(null,!1,d):d()}},onOptionHover:function(a){this.ignoreHover||this.setActiveOption(a.currentTarget,!1)},onOptionSelect:function(b){var c,d,e=this;b.preventDefault&&(b.preventDefault(),b.stopPropagation()),d=a(b.currentTarget),d.hasClass("create")?e.createItem(null,function(){e.settings.closeAfterSelect&&e.close()}):void 0!==(c=d.attr("data-value"))&&(e.lastQuery=null,e.setTextboxValue(""),e.addItem(c),e.settings.closeAfterSelect?e.close():!e.settings.hideSelected&&b.type&&/mouse/.test(b.type)&&e.setActiveOption(e.getOption(c)))},onItemSelect:function(a){var b=this;b.isLocked||"multi"===b.settings.mode&&(a.preventDefault(),b.setActiveItem(a.currentTarget,a))},load:function(a){var b=this,c=b.$wrapper.addClass(b.settings.loadingClass);b.loading++,a.apply(b,[function(a){b.loading=Math.max(b.loading-1,0),a&&a.length&&(b.addOption(a),b.refreshOptions(b.isFocused&&!b.isInputHidden)),b.loading||c.removeClass(b.settings.loadingClass),b.trigger("load",a)}])},setTextboxValue:function(a){var b=this.$control_input;b.val()!==a&&(b.val(a).triggerHandler("update"),this.lastValue=a)},getValue:function(){return 1===this.tagType&&this.$input.attr("multiple")?this.items:this.items.join(this.settings.delimiter)},setValue:function(a,b){p(this,b?[]:["change"],function(){this.clear(b),this.addItems(a,b)})},setActiveItem:function(b,c){var d,e,f,g,h,i,j,k,l=this;if("single"!==l.settings.mode){if(b=a(b),!b.length)return a(l.$activeItems).removeClass("active"),l.$activeItems=[],void(l.isFocused&&l.showInput());if("mousedown"===(d=c&&c.type.toLowerCase())&&l.isShiftDown&&l.$activeItems.length){for(k=l.$control.children(".active:last"),g=Array.prototype.indexOf.apply(l.$control[0].childNodes,[k[0]]),h=Array.prototype.indexOf.apply(l.$control[0].childNodes,[b[0]]),g>h&&(j=g,g=h,h=j),e=g;e<=h;e++)i=l.$control[0].childNodes[e],-1===l.$activeItems.indexOf(i)&&(a(i).addClass("active"),l.$activeItems.push(i));c.preventDefault()}else"mousedown"===d&&l.isCtrlDown||"keydown"===d&&this.isShiftDown?b.hasClass("active")?(f=l.$activeItems.indexOf(b[0]),l.$activeItems.splice(f,1),b.removeClass("active")):l.$activeItems.push(b.addClass("active")[0]):(a(l.$activeItems).removeClass("active"),l.$activeItems=[b.addClass("active")[0]]);l.hideInput(),this.isFocused||l.focus()}},setActiveOption:function(b,c,d){var e,f,g,h,i,k=this;k.$activeOption&&k.$activeOption.removeClass("active"),k.$activeOption=null,b=a(b),b.length&&(k.$activeOption=b.addClass("active"),!c&&j(c)||(e=k.$dropdown_content.height(),f=k.$activeOption.outerHeight(!0),c=k.$dropdown_content.scrollTop()||0,g=k.$activeOption.offset().top-k.$dropdown_content.offset().top+c,h=g,i=g-e+f,g+f>e+c?k.$dropdown_content.stop().animate({scrollTop:i},d?k.settings.scrollDuration:0):g<c&&k.$dropdown_content.stop().animate({scrollTop:h},d?k.settings.scrollDuration:0)))},selectAll:function(){var a=this;"single"!==a.settings.mode&&(a.$activeItems=Array.prototype.slice.apply(a.$control.children(":not(input)").addClass("active")),a.$activeItems.length&&(a.hideInput(),a.close()),a.focus())},hideInput:function(){var a=this;a.setTextboxValue(""),a.$control_input.css({opacity:0,position:"absolute",left:a.rtl?1e4:-1e4}),a.isInputHidden=!0},showInput:function(){this.$control_input.css({opacity:1,position:"relative",left:0}),this.isInputHidden=!1},focus:function(){var a=this;a.isDisabled||(a.ignoreFocus=!0,a.$control_input[0].focus(),window.setTimeout(function(){a.ignoreFocus=!1,a.onFocus()},0))},blur:function(a){this.$control_input[0].blur(),this.onBlur(null,a)},getScoreFunction:function(a){return this.sifter.getScoreFunction(a,this.getSearchOptions())},getSearchOptions:function(){var a=this.settings,b=a.sortField;return"string"==typeof b&&(b=[{field:b}]),{fields:a.searchField,conjunction:a.searchConjunction,sort:b,nesting:a.nesting}},search:function(b){var c,d,e,f=this,g=f.settings,h=this.getSearchOptions();if(g.score&&"function"!=typeof(e=f.settings.score.apply(this,[b])))throw new Error('Selectize "score" setting must be a function that returns a function');if(b!==f.lastQuery?(f.lastQuery=b,d=f.sifter.search(b,a.extend(h,{score:e})),f.currentResults=d):d=a.extend(!0,{},f.currentResults),g.hideSelected)for(c=d.items.length-1;c>=0;c--)-1!==f.items.indexOf(k(d.items[c].id))&&d.items.splice(c,1);return d},refreshOptions:function(b){var c,e,f,g,h,i,j,l,m,n,o,p,q,r,s,t;void 0===b&&(b=!0);var u=this,w=a.trim(u.$control_input.val()),x=u.search(w),y=u.$dropdown_content,z=u.$activeOption&&k(u.$activeOption.attr("data-value"));for(g=x.items.length,"number"==typeof u.settings.maxOptions&&(g=Math.min(g,u.settings.maxOptions)),h={},i=[],c=0;c<g;c++)for(j=u.options[x.items[c].id],l=u.render("option",j),m=j[u.settings.optgroupField]||"",n=a.isArray(m)?m:[m],e=0,f=n&&n.length;e<f;e++)m=n[e],u.optgroups.hasOwnProperty(m)||(m=""),h.hasOwnProperty(m)||(h[m]=document.createDocumentFragment(),i.push(m)),h[m].appendChild(l);for(this.settings.lockOptgroupOrder&&i.sort(function(a,b){return(u.optgroups[a].$order||0)-(u.optgroups[b].$order||0)}),o=document.createDocumentFragment(),c=0,g=i.length;c<g;c++)m=i[c],u.optgroups.hasOwnProperty(m)&&h[m].childNodes.length?(p=document.createDocumentFragment(),p.appendChild(u.render("optgroup_header",u.optgroups[m])),p.appendChild(h[m]),o.appendChild(u.render("optgroup",a.extend({},u.optgroups[m],{html:v(p),dom:p})))):o.appendChild(h[m]);if(y.html(o),u.settings.highlight&&(y.removeHighlight(),x.query.length&&x.tokens.length))for(c=0,g=x.tokens.length;c<g;c++)d(y,x.tokens[c].regex);if(!u.settings.hideSelected)for(c=0,g=u.items.length;c<g;c++)u.getOption(u.items[c]).addClass("selected");q=u.canCreate(w),q&&(y.prepend(u.render("option_create",{input:w})),t=a(y[0].childNodes[0])),u.hasOptions=x.items.length>0||q,u.hasOptions?(x.items.length>0?(s=z&&u.getOption(z),s&&s.length?r=s:"single"===u.settings.mode&&u.items.length&&(r=u.getOption(u.items[0])),r&&r.length||(r=t&&!u.settings.addPrecedence?u.getAdjacentOption(t,1):y.find("[data-selectable]:first"))):r=t,u.setActiveOption(r),b&&!u.isOpen&&u.open()):(u.setActiveOption(null),b&&u.isOpen&&u.close())},addOption:function(b){var c,d,e,f=this;if(a.isArray(b))for(c=0,d=b.length;c<d;c++)f.addOption(b[c]);else(e=f.registerOption(b))&&(f.userOptions[e]=!0,f.lastQuery=null,f.trigger("option_add",e,b))},registerOption:function(a){var b=k(a[this.settings.valueField]);return void 0!==b&&null!==b&&!this.options.hasOwnProperty(b)&&(a.$order=a.$order||++this.order,this.options[b]=a,b)},registerOptionGroup:function(a){var b=k(a[this.settings.optgroupValueField]);return!!b&&(a.$order=a.$order||++this.order,this.optgroups[b]=a,b)},addOptionGroup:function(a,b){b[this.settings.optgroupValueField]=a,(a=this.registerOptionGroup(b))&&this.trigger("optgroup_add",a,b)},removeOptionGroup:function(a){this.optgroups.hasOwnProperty(a)&&(delete this.optgroups[a],this.renderCache={},this.trigger("optgroup_remove",a))},clearOptionGroups:function(){this.optgroups={},this.renderCache={},this.trigger("optgroup_clear")},updateOption:function(b,c){var d,e,f,g,h,i,j,l=this;if(b=k(b),f=k(c[l.settings.valueField]),null!==b&&l.options.hasOwnProperty(b)){if("string"!=typeof f)throw new Error("Value must be set in option data");j=l.options[b].$order,f!==b&&(delete l.options[b],-1!==(g=l.items.indexOf(b))&&l.items.splice(g,1,f)),c.$order=c.$order||j,l.options[f]=c,h=l.renderCache.item,i=l.renderCache.option,h&&(delete h[b],delete h[f]),i&&(delete i[b],delete i[f]),-1!==l.items.indexOf(f)&&(d=l.getItem(b),e=a(l.render("item",c)),d.hasClass("active")&&e.addClass("active"),d.replaceWith(e)),l.lastQuery=null,l.isOpen&&l.refreshOptions(!1)}},removeOption:function(a,b){var c=this;a=k(a);var d=c.renderCache.item,e=c.renderCache.option;d&&delete d[a],e&&delete e[a],delete c.userOptions[a],delete c.options[a],c.lastQuery=null,c.trigger("option_remove",a),c.removeItem(a,b)},clearOptions:function(){var b=this;b.loadedSearches={},b.userOptions={},b.renderCache={};var c=b.options;a.each(b.options,function(a,d){-1==b.items.indexOf(a)&&delete c[a]}),b.options=b.sifter.items=c,b.lastQuery=null,b.trigger("option_clear")},getOption:function(a){return this.getElementWithValue(a,this.$dropdown_content.find("[data-selectable]"))},getAdjacentOption:function(b,c){var d=this.$dropdown.find("[data-selectable]"),e=d.index(b)+c;return e>=0&&e<d.length?d.eq(e):a()},getElementWithValue:function(b,c){if(void 0!==(b=k(b))&&null!==b)for(var d=0,e=c.length;d<e;d++)if(c[d].getAttribute("data-value")===b)return a(c[d]);return a()},getItem:function(a){return this.getElementWithValue(a,this.$control.children())},addItems:function(b,c){this.buffer=document.createDocumentFragment();for(var d=this.$control[0].childNodes,e=0;e<d.length;e++)this.buffer.appendChild(d[e]);for(var f=a.isArray(b)?b:[b],e=0,g=f.length;e<g;e++)this.isPending=e<g-1,this.addItem(f[e],c);var h=this.$control[0];h.insertBefore(this.buffer,h.firstChild),this.buffer=null},addItem:function(b,c){p(this,c?[]:["change"],function(){var d,e,f,g,h,i=this,j=i.settings.mode;if(b=k(b),-1!==i.items.indexOf(b))return void("single"===j&&i.close());i.options.hasOwnProperty(b)&&("single"===j&&i.clear(c),"multi"===j&&i.isFull()||(d=a(i.render("item",i.options[b])),h=i.isFull(),i.items.splice(i.caretPos,0,b),i.insertAtCaret(d),(!i.isPending||!h&&i.isFull())&&i.refreshState(),i.isSetup&&(f=i.$dropdown_content.find("[data-selectable]"),i.isPending||(e=i.getOption(b),g=i.getAdjacentOption(e,1).attr("data-value"),i.refreshOptions(i.isFocused&&"single"!==j),g&&i.setActiveOption(i.getOption(g))),!f.length||i.isFull()?i.close():i.isPending||i.positionDropdown(),i.updatePlaceholder(),i.trigger("item_add",b,d),i.isPending||i.updateOriginalInput({silent:c}))))})},removeItem:function(b,c){var d,e,f,g=this;d=b instanceof a?b:g.getItem(b),b=k(d.attr("data-value")),-1!==(e=g.items.indexOf(b))&&(d.remove(),d.hasClass("active")&&(f=g.$activeItems.indexOf(d[0]),g.$activeItems.splice(f,1)),g.items.splice(e,1),g.lastQuery=null,!g.settings.persist&&g.userOptions.hasOwnProperty(b)&&g.removeOption(b,c),e<g.caretPos&&g.setCaret(g.caretPos-1),g.refreshState(),g.updatePlaceholder(),g.updateOriginalInput({silent:c}),g.positionDropdown(),g.trigger("item_remove",b,d))},createItem:function(b,c){var d=this,e=d.caretPos;b=b||a.trim(d.$control_input.val()||"");var f=arguments[arguments.length-1];if("function"!=typeof f&&(f=function(){}),"boolean"!=typeof c&&(c=!0),!d.canCreate(b))return f(),!1;d.lock();var g="function"==typeof d.settings.create?this.settings.create:function(a){var b={};return b[d.settings.labelField]=a,b[d.settings.valueField]=a,b},h=n(function(a){if(d.unlock(),!a||"object"!=typeof a)return f();var b=k(a[d.settings.valueField]);if("string"!=typeof b)return f();d.setTextboxValue(""),d.addOption(a),d.setCaret(e),d.addItem(b),d.refreshOptions(c&&"single"!==d.settings.mode),f(a)}),i=g.apply(this,[b,h]);return void 0!==i&&h(i),!0},refreshItems:function(){this.lastQuery=null,this.isSetup&&this.addItem(this.items),this.refreshState(),this.updateOriginalInput()},refreshState:function(){this.refreshValidityState(),this.refreshClasses()},refreshValidityState:function(){if(!this.isRequired)return!1;var a=!this.items.length;this.isInvalid=a,this.$control_input.prop("required",a),this.$input.prop("required",!a)},refreshClasses:function(){var b=this,c=b.isFull(),d=b.isLocked;b.$wrapper.toggleClass("rtl",b.rtl),b.$control.toggleClass("focus",b.isFocused).toggleClass("disabled",b.isDisabled).toggleClass("required",b.isRequired).toggleClass("invalid",b.isInvalid).toggleClass("locked",d).toggleClass("full",c).toggleClass("not-full",!c).toggleClass("input-active",b.isFocused&&!b.isInputHidden).toggleClass("dropdown-active",b.isOpen).toggleClass("has-options",!a.isEmptyObject(b.options)).toggleClass("has-items",b.items.length>0),b.$control_input.data("grow",!c&&!d)},isFull:function(){
+return null!==this.settings.maxItems&&this.items.length>=this.settings.maxItems},updateOriginalInput:function(a){var b,c,d,e,f=this;if(a=a||{},1===f.tagType){for(d=[],b=0,c=f.items.length;b<c;b++)e=f.options[f.items[b]][f.settings.labelField]||"",d.push('<option value="'+l(f.items[b])+'" selected="selected">'+l(e)+"</option>");d.length||this.$input.attr("multiple")||d.push('<option value="" selected="selected"></option>'),f.$input.html(d.join(""))}else f.$input.val(f.getValue()),f.$input.attr("value",f.$input.val());f.isSetup&&(a.silent||f.trigger("change",f.$input.val()))},updatePlaceholder:function(){if(this.settings.placeholder){var a=this.$control_input;this.items.length?a.removeAttr("placeholder"):a.attr("placeholder",this.settings.placeholder),a.triggerHandler("update",{force:!0})}},open:function(){var a=this;a.isLocked||a.isOpen||"multi"===a.settings.mode&&a.isFull()||(a.focus(),a.isOpen=!0,a.refreshState(),a.$dropdown.css({visibility:"hidden",display:"block"}),a.positionDropdown(),a.$dropdown.css({visibility:"visible"}),a.trigger("dropdown_open",a.$dropdown))},close:function(){var a=this,b=a.isOpen;"single"===a.settings.mode&&a.items.length&&(a.hideInput(),a.isBlurring||a.$control_input.blur()),a.isOpen=!1,a.$dropdown.hide(),a.setActiveOption(null),a.refreshState(),b&&a.trigger("dropdown_close",a.$dropdown)},positionDropdown:function(){var a=this.$control,b="body"===this.settings.dropdownParent?a.offset():a.position();b.top+=a.outerHeight(!0),this.$dropdown.css({width:a[0].getBoundingClientRect().width,top:b.top,left:b.left})},clear:function(a){var b=this;b.items.length&&(b.$control.children(":not(input)").remove(),b.items=[],b.lastQuery=null,b.setCaret(0),b.setActiveItem(null),b.updatePlaceholder(),b.updateOriginalInput({silent:a}),b.refreshState(),b.showInput(),b.trigger("clear"))},insertAtCaret:function(a){var b=Math.min(this.caretPos,this.items.length),c=a[0],d=this.buffer||this.$control[0];0===b?d.insertBefore(c,d.firstChild):d.insertBefore(c,d.childNodes[b]),this.setCaret(b+1)},deleteSelection:function(b){var c,d,e,f,g,h,i,j,k,l=this;if(e=b&&8===b.keyCode?-1:1,f=r(l.$control_input[0]),l.$activeOption&&!l.settings.hideSelected&&(i=l.getAdjacentOption(l.$activeOption,-1).attr("data-value")),g=[],l.$activeItems.length){for(k=l.$control.children(".active:"+(e>0?"last":"first")),h=l.$control.children(":not(input)").index(k),e>0&&h++,c=0,d=l.$activeItems.length;c<d;c++)g.push(a(l.$activeItems[c]).attr("data-value"));b&&(b.preventDefault(),b.stopPropagation())}else(l.isFocused||"single"===l.settings.mode)&&l.items.length&&(e<0&&0===f.start&&0===f.length?g.push(l.items[l.caretPos-1]):e>0&&f.start===l.$control_input.val().length&&g.push(l.items[l.caretPos]));if(!g.length||"function"==typeof l.settings.onDelete&&!1===l.settings.onDelete.apply(l,[g]))return!1;for(void 0!==h&&l.setCaret(h);g.length;)l.removeItem(g.pop());return l.showInput(),l.positionDropdown(),l.refreshOptions(!0),i&&(j=l.getOption(i),j.length&&l.setActiveOption(j)),!0},advanceSelection:function(a,b){var c,d,e,f,g,h=this;0!==a&&(h.rtl&&(a*=-1),c=a>0?"last":"first",d=r(h.$control_input[0]),h.isFocused&&!h.isInputHidden?(f=h.$control_input.val().length,(a<0?0===d.start&&0===d.length:d.start===f)&&!f&&h.advanceCaret(a,b)):(g=h.$control.children(".active:"+c),g.length&&(e=h.$control.children(":not(input)").index(g),h.setActiveItem(null),h.setCaret(a>0?e+1:e))))},advanceCaret:function(a,b){var c,d,e=this;0!==a&&(c=a>0?"next":"prev",e.isShiftDown?(d=e.$control_input[c](),d.length&&(e.hideInput(),e.setActiveItem(d),b&&b.preventDefault())):e.setCaret(e.caretPos+a))},setCaret:function(b){var c=this;if(b="single"===c.settings.mode?c.items.length:Math.max(0,Math.min(c.items.length,b)),!c.isPending){var d,e,f,g;for(f=c.$control.children(":not(input)"),d=0,e=f.length;d<e;d++)g=a(f[d]).detach(),d<b?c.$control_input.before(g):c.$control.append(g)}c.caretPos=b},lock:function(){this.close(),this.isLocked=!0,this.refreshState()},unlock:function(){this.isLocked=!1,this.refreshState()},disable:function(){var a=this;a.$input.prop("disabled",!0),a.$control_input.prop("disabled",!0).prop("tabindex",-1),a.isDisabled=!0,a.lock()},enable:function(){var a=this;a.$input.prop("disabled",!1),a.$control_input.prop("disabled",!1).prop("tabindex",a.tabIndex),a.isDisabled=!1,a.unlock()},destroy:function(){var b=this,c=b.eventNS,d=b.revertSettings;b.trigger("destroy"),b.off(),b.$wrapper.remove(),b.$dropdown.remove(),b.$input.html("").append(d.$children).removeAttr("tabindex").removeClass("selectized").attr({tabindex:d.tabindex}).show(),b.$control_input.removeData("grow"),b.$input.removeData("selectize"),0==--w.count&&w.$testInput&&(w.$testInput.remove(),w.$testInput=void 0),a(window).off(c),a(document).off(c),a(document.body).off(c),delete b.$input[0].selectize},render:function(b,c){var d,e,f="",g=!1,h=this;return"option"!==b&&"item"!==b||(d=k(c[h.settings.valueField]),g=!!d),g&&(j(h.renderCache[b])||(h.renderCache[b]={}),h.renderCache[b].hasOwnProperty(d))?h.renderCache[b][d]:(f=a(h.settings.render[b].apply(this,[c,l])),"option"===b||"option_create"===b?c[h.settings.disabledField]||f.attr("data-selectable",""):"optgroup"===b&&(e=c[h.settings.optgroupValueField]||"",f.attr("data-group",e),c[h.settings.disabledField]&&f.attr("data-disabled","")),"option"!==b&&"item"!==b||f.attr("data-value",d||""),g&&(h.renderCache[b][d]=f[0]),f[0])},clearCache:function(a){var b=this;void 0===a?b.renderCache={}:delete b.renderCache[a]},canCreate:function(a){var b=this;if(!b.settings.create)return!1;var c=b.settings.createFilter;return a.length&&("function"!=typeof c||c.apply(b,[a]))&&("string"!=typeof c||new RegExp(c).test(a))&&(!(c instanceof RegExp)||c.test(a))}}),w.count=0,w.defaults={options:[],optgroups:[],plugins:[],delimiter:",",splitOn:null,persist:!0,diacritics:!0,create:!1,createOnBlur:!1,createFilter:null,highlight:!0,openOnFocus:!0,maxOptions:1e3,maxItems:null,hideSelected:null,addPrecedence:!1,selectOnTab:!1,preload:!1,allowEmptyOption:!1,closeAfterSelect:!1,scrollDuration:60,loadThrottle:300,loadingClass:"loading",dataAttr:"data-data",optgroupField:"optgroup",valueField:"value",labelField:"text",disabledField:"disabled",optgroupLabelField:"label",optgroupValueField:"value",lockOptgroupOrder:!1,sortField:"$order",searchField:["text"],searchConjunction:"and",mode:null,wrapperClass:"selectize-control",inputClass:"selectize-input",dropdownClass:"selectize-dropdown",dropdownContentClass:"selectize-dropdown-content",dropdownParent:null,copyClassesToDropdown:!0,render:{}},a.fn.selectize=function(b){var c=a.fn.selectize.defaults,d=a.extend({},c,b),e=d.dataAttr,f=d.labelField,g=d.valueField,h=d.disabledField,i=d.optgroupField,j=d.optgroupLabelField,l=d.optgroupValueField,m=function(b,c){var h,i,j,k,l=b.attr(e);if(l)for(c.options=JSON.parse(l),h=0,i=c.options.length;h<i;h++)c.items.push(c.options[h][g]);else{var m=a.trim(b.val()||"");if(!d.allowEmptyOption&&!m.length)return;for(j=m.split(d.delimiter),h=0,i=j.length;h<i;h++)k={},k[f]=j[h],k[g]=j[h],c.options.push(k);c.items=j}},n=function(b,c){var m,n,o,p,q=c.options,r={},s=function(a){var b=e&&a.attr(e);return"string"==typeof b&&b.length?JSON.parse(b):null},t=function(b,e){b=a(b);var j=k(b.val());if(j||d.allowEmptyOption)if(r.hasOwnProperty(j)){if(e){var l=r[j][i];l?a.isArray(l)?l.push(e):r[j][i]=[l,e]:r[j][i]=e}}else{var m=s(b)||{};m[f]=m[f]||b.text(),m[g]=m[g]||j,m[h]=m[h]||b.prop("disabled"),m[i]=m[i]||e,r[j]=m,q.push(m),b.is(":selected")&&c.items.push(j)}};for(c.maxItems=b.attr("multiple")?null:1,p=b.children(),m=0,n=p.length;m<n;m++)o=p[m].tagName.toLowerCase(),"optgroup"===o?function(b){var d,e,f,g,i;for(b=a(b),f=b.attr("label"),f&&(g=s(b)||{},g[j]=f,g[l]=f,g[h]=b.prop("disabled"),c.optgroups.push(g)),i=a("option",b),d=0,e=i.length;d<e;d++)t(i[d],f)}(p[m]):"option"===o&&t(p[m])};return this.each(function(){if(!this.selectize){var e=a(this),f=this.tagName.toLowerCase(),g=e.attr("placeholder")||e.attr("data-placeholder");g||d.allowEmptyOption||(g=e.children('option[value=""]').text());var h={placeholder:g,options:[],optgroups:[],items:[]};"select"===f?n(e,h):m(e,h),new w(e,a.extend(!0,{},c,h,b))}})},a.fn.selectize.defaults=w.defaults,a.fn.selectize.support={validity:i},w.define("drag_drop",function(b){if(!a.fn.sortable)throw new Error('The "drag_drop" plugin requires jQuery UI "sortable".');if("multi"===this.settings.mode){var c=this;c.lock=function(){var a=c.lock;return function(){var b=c.$control.data("sortable");return b&&b.disable(),a.apply(c,arguments)}}(),c.unlock=function(){var a=c.unlock;return function(){var b=c.$control.data("sortable");return b&&b.enable(),a.apply(c,arguments)}}(),c.setup=function(){var b=c.setup;return function(){b.apply(this,arguments);var d=c.$control.sortable({items:"[data-value]",forcePlaceholderSize:!0,disabled:c.isLocked,start:function(a,b){b.placeholder.css("width",b.helper.css("width")),d.css({overflow:"visible"})},stop:function(){d.css({overflow:"hidden"});var b=c.$activeItems?c.$activeItems.slice():null,e=[];d.children("[data-value]").each(function(){e.push(a(this).attr("data-value"))}),c.setValue(e),c.setActiveItem(b)}})}}()}}),w.define("dropdown_header",function(b){var c=this;b=a.extend({title:"Untitled",headerClass:"selectize-dropdown-header",titleRowClass:"selectize-dropdown-header-title",labelClass:"selectize-dropdown-header-label",closeClass:"selectize-dropdown-header-close",html:function(a){return'<div class="'+a.headerClass+'"><div class="'+a.titleRowClass+'"><span class="'+a.labelClass+'">'+a.title+'</span><a href="javascript:void(0)" class="'+a.closeClass+'">&times;</a></div></div>'}},b),c.setup=function(){var d=c.setup;return function(){d.apply(c,arguments),c.$dropdown_header=a(b.html(b)),c.$dropdown.prepend(c.$dropdown_header)}}()}),w.define("optgroup_columns",function(b){var c=this;b=a.extend({equalizeWidth:!0,equalizeHeight:!0},b),this.getAdjacentOption=function(b,c){var d=b.closest("[data-group]").find("[data-selectable]"),e=d.index(b)+c;return e>=0&&e<d.length?d.eq(e):a()},this.onKeyDown=function(){var a=c.onKeyDown;return function(b){var d,e,f,g;return!this.isOpen||37!==b.keyCode&&39!==b.keyCode?a.apply(this,arguments):(c.ignoreHover=!0,g=this.$activeOption.closest("[data-group]"),d=g.find("[data-selectable]").index(this.$activeOption),g=37===b.keyCode?g.prev("[data-group]"):g.next("[data-group]"),f=g.find("[data-selectable]"),e=f.eq(Math.min(f.length-1,d)),void(e.length&&this.setActiveOption(e)))}}();var d=function(){var a,b=d.width,c=document;return void 0===b&&(a=c.createElement("div"),a.innerHTML='<div style="width:50px;height:50px;position:absolute;left:-50px;top:-50px;overflow:auto;"><div style="width:1px;height:100px;"></div></div>',a=a.firstChild,c.body.appendChild(a),b=d.width=a.offsetWidth-a.clientWidth,c.body.removeChild(a)),b},e=function(){var e,f,g,h,i,j,k;if(k=a("[data-group]",c.$dropdown_content),(f=k.length)&&c.$dropdown_content.width()){if(b.equalizeHeight){for(g=0,e=0;e<f;e++)g=Math.max(g,k.eq(e).height());k.css({height:g})}b.equalizeWidth&&(j=c.$dropdown_content.innerWidth()-d(),h=Math.round(j/f),k.css({width:h}),f>1&&(i=j-h*(f-1),k.eq(f-1).css({width:i})))}};(b.equalizeHeight||b.equalizeWidth)&&(m.after(this,"positionDropdown",e),m.after(this,"refreshOptions",e))}),w.define("remove_button",function(b){b=a.extend({label:"&times;",title:"Remove",className:"remove",append:!0},b);if("single"===this.settings.mode)return void function(b,c){c.className="remove-single";var d=b,e='<a href="javascript:void(0)" class="'+c.className+'" tabindex="-1" title="'+l(c.title)+'">'+c.label+"</a>",f=function(b,c){return a("<span>").append(b).append(c)};b.setup=function(){var g=d.setup;return function(){if(c.append){var h=a(d.$input.context).attr("id"),i=(a("#"+h),d.settings.render.item);d.settings.render.item=function(a){return f(i.apply(b,arguments),e)}}g.apply(b,arguments),b.$control.on("click","."+c.className,function(a){a.preventDefault(),d.isLocked||d.clear()})}}()}(this,b);!function(b,c){var d=b,e='<a href="javascript:void(0)" class="'+c.className+'" tabindex="-1" title="'+l(c.title)+'">'+c.label+"</a>",f=function(a,b){var c=a.search(/(<\/[^>]+>\s*)$/);return a.substring(0,c)+b+a.substring(c)};b.setup=function(){var g=d.setup;return function(){if(c.append){var h=d.settings.render.item;d.settings.render.item=function(a){return f(h.apply(b,arguments),e)}}g.apply(b,arguments),b.$control.on("click","."+c.className,function(b){if(b.preventDefault(),!d.isLocked){var c=a(b.currentTarget).parent();d.setActiveItem(c),d.deleteSelection()&&d.setCaret(d.items.length)}})}}()}(this,b)}),w.define("restore_on_backspace",function(a){var b=this;a.text=a.text||function(a){return a[this.settings.labelField]},this.onKeyDown=function(){var c=b.onKeyDown;return function(b){var d,e;return 8===b.keyCode&&""===this.$control_input.val()&&!this.$activeItems.length&&(d=this.caretPos-1)>=0&&d<this.items.length?(e=this.options[this.items[d]],this.deleteSelection(b)&&(this.setTextboxValue(a.text.apply(this,[e])),this.refreshOptions(!0)),void b.preventDefault()):c.apply(this,arguments)}}()}),w});
diff --git a/modules/http/static/selectize.spdx b/modules/http/static/selectize.spdx
new file mode 100644
index 0000000..594c413
--- /dev/null
+++ b/modules/http/static/selectize.spdx
@@ -0,0 +1,11 @@
+SPDXVersion: SPDX-2.1
+DataLicense: CC0-1.0
+SPDXID: SPDXRef-DOCUMENT
+DocumentName: selectize.js
+DocumentNamespace: http://spdx.org/spdxdocs/spdx-v2.1-5ffbfb08-4009-4a41-bc96-16adb5f2b632
+
+PackageName: selectize.js
+PackageVersion: 0.12.6
+PackageDownloadLocation: git+https://github.com/selectize/selectize.js.git@eb0fca364f9bd6864ae197ea58c853f2717052a9#dist/
+PackageOriginator: Person: Jonathan Allard (jonathan@allard.io)
+PackageLicenseDeclared: Apache-2.0
diff --git a/modules/http/static/topojson.js b/modules/http/static/topojson.js
new file mode 100644
index 0000000..f66214b
--- /dev/null
+++ b/modules/http/static/topojson.js
@@ -0,0 +1,2 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+!function(){function t(n,t){function r(t){var r,e=n.arcs[0>t?~t:t],o=e[0];return n.transform?(r=[0,0],e.forEach(function(n){r[0]+=n[0],r[1]+=n[1]})):r=e[e.length-1],0>t?[r,o]:[o,r]}function e(n,t){for(var r in n){var e=n[r];delete t[e.start],delete e.start,delete e.end,e.forEach(function(n){o[0>n?~n:n]=1}),f.push(e)}}var o={},i={},u={},f=[],c=-1;return t.forEach(function(r,e){var o,i=n.arcs[0>r?~r:r];i.length<3&&!i[1][0]&&!i[1][1]&&(o=t[++c],t[c]=r,t[e]=o)}),t.forEach(function(n){var t,e,o=r(n),f=o[0],c=o[1];if(t=u[f])if(delete u[t.end],t.push(n),t.end=c,e=i[c]){delete i[e.start];var a=e===t?t:t.concat(e);i[a.start=t.start]=u[a.end=e.end]=a}else i[t.start]=u[t.end]=t;else if(t=i[c])if(delete i[t.start],t.unshift(n),t.start=f,e=u[f]){delete u[e.end];var s=e===t?t:e.concat(t);i[s.start=e.start]=u[s.end=t.end]=s}else i[t.start]=u[t.end]=t;else t=[n],i[t.start=f]=u[t.end=c]=t}),e(u,i),e(i,u),t.forEach(function(n){o[0>n?~n:n]||f.push([n])}),f}function r(n,r,e){function o(n){var t=0>n?~n:n;(s[t]||(s[t]=[])).push({i:n,g:a})}function i(n){n.forEach(o)}function u(n){n.forEach(i)}function f(n){"GeometryCollection"===n.type?n.geometries.forEach(f):n.type in l&&(a=n,l[n.type](n.arcs))}var c=[];if(arguments.length>1){var a,s=[],l={LineString:i,MultiLineString:u,Polygon:u,MultiPolygon:function(n){n.forEach(u)}};f(r),s.forEach(arguments.length<3?function(n){c.push(n[0].i)}:function(n){e(n[0].g,n[n.length-1].g)&&c.push(n[0].i)})}else for(var h=0,p=n.arcs.length;p>h;++h)c.push(h);return{type:"MultiLineString",arcs:t(n,c)}}function e(r,e){function o(n){n.forEach(function(t){t.forEach(function(t){(f[t=0>t?~t:t]||(f[t]=[])).push(n)})}),c.push(n)}function i(n){return l(u(r,{type:"Polygon",arcs:[n]}).coordinates[0])>0}var f={},c=[],a=[];return e.forEach(function(n){"Polygon"===n.type?o(n.arcs):"MultiPolygon"===n.type&&n.arcs.forEach(o)}),c.forEach(function(n){if(!n._){var t=[],r=[n];for(n._=1,a.push(t);n=r.pop();)t.push(n),n.forEach(function(n){n.forEach(function(n){f[0>n?~n:n].forEach(function(n){n._||(n._=1,r.push(n))})})})}}),c.forEach(function(n){delete n._}),{type:"MultiPolygon",arcs:a.map(function(e){var o=[];if(e.forEach(function(n){n.forEach(function(n){n.forEach(function(n){f[0>n?~n:n].length<2&&o.push(n)})})}),o=t(r,o),(n=o.length)>1)for(var u,c=i(e[0][0]),a=0;n>a;++a)if(c===i(o[a])){u=o[0],o[0]=o[a],o[a]=u;break}return o})}}function o(n,t){return"GeometryCollection"===t.type?{type:"FeatureCollection",features:t.geometries.map(function(t){return i(n,t)})}:i(n,t)}function i(n,t){var r={type:"Feature",id:t.id,properties:t.properties||{},geometry:u(n,t)};return null==t.id&&delete r.id,r}function u(n,t){function r(n,t){t.length&&t.pop();for(var r,e=s[0>n?~n:n],o=0,i=e.length;i>o;++o)t.push(r=e[o].slice()),a(r,o);0>n&&f(t,i)}function e(n){return n=n.slice(),a(n,0),n}function o(n){for(var t=[],e=0,o=n.length;o>e;++e)r(n[e],t);return t.length<2&&t.push(t[0].slice()),t}function i(n){for(var t=o(n);t.length<4;)t.push(t[0].slice());return t}function u(n){return n.map(i)}function c(n){var t=n.type;return"GeometryCollection"===t?{type:t,geometries:n.geometries.map(c)}:t in l?{type:t,coordinates:l[t](n)}:null}var a=g(n.transform),s=n.arcs,l={Point:function(n){return e(n.coordinates)},MultiPoint:function(n){return n.coordinates.map(e)},LineString:function(n){return o(n.arcs)},MultiLineString:function(n){return n.arcs.map(o)},Polygon:function(n){return u(n.arcs)},MultiPolygon:function(n){return n.arcs.map(u)}};return c(t)}function f(n,t){for(var r,e=n.length,o=e-t;o<--e;)r=n[o],n[o++]=n[e],n[e]=r}function c(n,t){for(var r=0,e=n.length;e>r;){var o=r+e>>>1;n[o]<t?r=o+1:e=o}return r}function a(n){function t(n,t){n.forEach(function(n){0>n&&(n=~n);var r=o[n];r?r.push(t):o[n]=[t]})}function r(n,r){n.forEach(function(n){t(n,r)})}function e(n,t){"GeometryCollection"===n.type?n.geometries.forEach(function(n){e(n,t)}):n.type in u&&u[n.type](n.arcs,t)}var o={},i=n.map(function(){return[]}),u={LineString:t,MultiLineString:r,Polygon:r,MultiPolygon:function(n,t){n.forEach(function(n){r(n,t)})}};n.forEach(e);for(var f in o)for(var a=o[f],s=a.length,l=0;s>l;++l)for(var h=l+1;s>h;++h){var p,v=a[l],g=a[h];(p=i[v])[f=c(p,g)]!==g&&p.splice(f,0,g),(p=i[g])[f=c(p,v)]!==v&&p.splice(f,0,v)}return i}function s(n,t){function r(n){u.remove(n),n[1][2]=t(n),u.push(n)}var e,o=g(n.transform),i=m(n.transform),u=v(),f=0;for(t||(t=h),n.arcs.forEach(function(n){var r=[];n.forEach(o);for(var i=1,f=n.length-1;f>i;++i)e=n.slice(i-1,i+2),e[1][2]=t(e),r.push(e),u.push(e);n[0][2]=n[f][2]=1/0;for(var i=0,f=r.length;f>i;++i)e=r[i],e.previous=r[i-1],e.next=r[i+1]});e=u.pop();){var c=e.previous,a=e.next;e[1][2]<f?e[1][2]=f:f=e[1][2],c&&(c.next=a,c[2]=e[2],r(c)),a&&(a.previous=c,a[0]=e[0],r(a))}return n.arcs.forEach(function(n){n.forEach(i)}),n}function l(n){for(var t,r=-1,e=n.length,o=n[e-1],i=0;++r<e;)t=o,o=n[r],i+=t[0]*o[1]-t[1]*o[0];return.5*i}function h(n){var t=n[0],r=n[1],e=n[2];return Math.abs((t[0]-e[0])*(r[1]-t[1])-(t[0]-r[0])*(e[1]-t[1]))}function p(n,t){return n[1][2]-t[1][2]}function v(){function n(n,t){for(;t>0;){var r=(t+1>>1)-1,o=e[r];if(p(n,o)>=0)break;e[o._=t]=o,e[n._=t=r]=n}}function t(n,t){for(;;){var r=t+1<<1,i=r-1,u=t,f=e[u];if(o>i&&p(e[i],f)<0&&(f=e[u=i]),o>r&&p(e[r],f)<0&&(f=e[u=r]),u===t)break;e[f._=t]=f,e[n._=t=u]=n}}var r={},e=[],o=0;return r.push=function(t){return n(e[t._=o]=t,o++),o},r.pop=function(){if(!(0>=o)){var n,r=e[0];return--o>0&&(n=e[o],t(e[n._=0]=n,0)),r}},r.remove=function(r){var i,u=r._;if(e[u]===r)return u!==--o&&(i=e[o],(p(i,r)<0?n:t)(e[i._=u]=i,u)),u},r}function g(n){if(!n)return y;var t,r,e=n.scale[0],o=n.scale[1],i=n.translate[0],u=n.translate[1];return function(n,f){f||(t=r=0),n[0]=(t+=n[0])*e+i,n[1]=(r+=n[1])*o+u}}function m(n){if(!n)return y;var t,r,e=n.scale[0],o=n.scale[1],i=n.translate[0],u=n.translate[1];return function(n,f){f||(t=r=0);var c=(n[0]-i)/e|0,a=(n[1]-u)/o|0;n[0]=c-t,n[1]=a-r,t=c,r=a}}function y(){}var d={version:"1.6.9",mesh:function(n){return u(n,r.apply(this,arguments))},meshArcs:r,merge:function(n){return u(n,e.apply(this,arguments))},mergeArcs:e,feature:o,neighbors:a,presimplify:s};"function"==typeof define&&define.amd?define(d):"object"==typeof module&&module.exports?module.exports=d:this.topojson=d}();
diff --git a/modules/http/static/topojson.spdx b/modules/http/static/topojson.spdx
new file mode 100644
index 0000000..5e5f302
--- /dev/null
+++ b/modules/http/static/topojson.spdx
@@ -0,0 +1,12 @@
+SPDXVersion: SPDX-2.1
+DataLicense: CC0-1.0
+SPDXID: SPDXRef-DOCUMENT
+DocumentName: topojson
+DocumentNamespace: http://spdx.org/spdxdocs/spdx-v2.1-2a02fcb5-2889-42c1-ac6a-2674fb1b7be0
+
+PackageName: topojson
+PackageVersion: 1.6.9
+PackageDownloadLocation: https://unpkg.com/topojson@1.6.9/topojson.min.js
+PackageChecksum: SHA256: 091bee8a099772d9a377ddbb4f2a43a026c4877e986d6c6a9291daf043b0b37e
+PackageOriginator: Person: Michael Bostock (mike@ocks.org)
+PackageLicenseDeclared: BSD-3-Clause
diff --git a/modules/http/test_tls/broken.crt b/modules/http/test_tls/broken.crt
new file mode 100644
index 0000000..d93d1f8
--- /dev/null
+++ b/modules/http/test_tls/broken.crt
@@ -0,0 +1,3 @@
+Æ¿¯ˆž³psâ$Ðëí¯Ö¡«Æȼ[ŒîØ1´Ç =fl:°Êl=z†=M}iÉ»¤Ñ­†÷*7ƒ)Ä Ñ5
+ÈóîÒj¨šIÜWÈÀ­®·´ MwÚf[´HïŽíÙÑÅ-Á¯ËìýEȼë€fû¦Ö   2fTKÊqýFѬUÖ ƒ“(jÀ¤œaà,Õœ¥*²X:lFÌÍ¿£Mî©>ó3Œš
+¬‘áD<^O™qk¥ˆQŽÉý‘κM…gÄ]pUNÉö¥MÝ>ŒŸÂÿº¹á(EI”®µ'ÌGÅ€÷mÕÅ÷Ã:ž3 º_”!‚ÁÎ’? 3Œú$H[„E†¡M¡4èòR¨†ÒA+0w¥ö0ÏÓÁ“%é¸eo¤ašåëØó(öw;¥oÇ¥î¯$—!ZèÀˆr%Äùàü&ßýh;1¾@ˆñý-9((Ób7±á\„UâoÃJÊ`»:Þª€†¼™~ŸdÃŽa•œƒìÜЃÇŒEÂœ—PB*l«Î}!q7ü;+QRL‘¢¶vÉûËQ‡[KãYûXðRà 2(¬íùªå7ÀÜ+$ëÃE,óõ­Ûý³IRÄÙï· §^4µD ¼_r…,išÁèÁ\ðhξ \ No newline at end of file
diff --git a/modules/http/test_tls/broken.key b/modules/http/test_tls/broken.key
new file mode 100644
index 0000000..ebcbfcf
--- /dev/null
+++ b/modules/http/test_tls/broken.key
Binary files differ
diff --git a/modules/http/test_tls/ca.crt b/modules/http/test_tls/ca.crt
new file mode 100644
index 0000000..81a42d2
--- /dev/null
+++ b/modules/http/test_tls/ca.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDVzCCAj+gAwIBAgIUAu0lPjQwQtB0QMMjaFfa+RrJPo0wDQYJKoZIhvcNAQEL
+BQAwMzELMAkGA1UEBhMCR0IxEDAOBgNVBAgMB0VuZ2xhbmQxEjAQBgNVBAoMCUFs
+aWNlIEx0ZDAeFw0xOTAzMjgxMjUzMjNaFw0zOTAzMjMxMjUzMjNaMDMxCzAJBgNV
+BAYTAkdCMRAwDgYDVQQIDAdFbmdsYW5kMRIwEAYDVQQKDAlBbGljZSBMdGQwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkLE9N62uDhi/zecTnGgQisDcg
+RuSgvU8Z/CVQC5WvSmZxO/RKw+TwSKigVMBiLKvi2gZwda4QZN1+If2/IPO41NvV
+Gc9vMffOpYCi3dLW9u3a3n7ZDHUlbmbYMyrkWcYEKC+wGBgvg9a1bce3VEvLGj/t
+YtTV4DPiUQz87jQxRp1a++XVvYkM6XpRIxfoyRdjW7ixssdpkd3DAsf2+qS35Ax8
+XTUvcoHuBcL2j4FAvptRk09T4S7bYLzx5cG0DBxhc75++n1T02D40D5uV7j2OI2N
+pQpsGhewrvS+9+F7z2f7Kpc1slJ7MW5eiYflF/R4gDxFTd0kZ9YWeRwwf1ltAgMB
+AAGjYzBhMB0GA1UdDgQWBBQRVncFgsUWq566JMAcejJj0t9uFjAfBgNVHSMEGDAW
+gBQRVncFgsUWq566JMAcejJj0t9uFjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB
+/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEANdM7o2phBhK5ZhRVjQQb7Ff+l1zn
+EZ5HVnn+YwL3y9h8nJsW5Y1xiIm1w/Apn+gqrBYUMchJxQ1/ha0Bza6kl29i2fck
+oAAaduTsZImiKCAMxlN8KpJm9DZ3woCwktG7orgzEFgVOiAqk6wbXaff1rAgoVDV
+nzWKh7AoyGkXxFI+wrJFODl92a+gy20pNZCDQqVtiC8AiHl2p+UrU8f7yzjyUyyl
+4ZKyvVne+SB4n4mug1JRLgxTM1C/Qp21DaYaCAYeqT7MZTd7d5nm0nWNOSlZ2lrZ
+e3RuQZic89G1OK9qvjfaUMvzm0iAE9hZ7d3ac1PU+KqRmi0TTO1y62M+rA==
+-----END CERTIFICATE-----
diff --git a/modules/http/test_tls/chain.crt b/modules/http/test_tls/chain.crt
new file mode 100644
index 0000000..1822e6d
--- /dev/null
+++ b/modules/http/test_tls/chain.crt
@@ -0,0 +1,41 @@
+-----BEGIN CERTIFICATE-----
+MIIDVjCCAj6gAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCR0Ix
+EDAOBgNVBAgMB0VuZ2xhbmQxEjAQBgNVBAoMCUFsaWNlIEx0ZDEYMBYGA1UEAwwP
+SW50ZXJtZWRpYXRlIENBMB4XDTE5MDYxNDA5NTAzOVoXDTM5MDYwOTA5NTAzOVow
+WDELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu
+dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDERMA8GA1UEAwwIa3IubG9jYWwwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVh57hcYaSjQVQ+cUPD++o4ws0vI+S
+2LGQ4u+YzRvx2o3r85DFklfYTM2OICJKEyMNsUm6c45gc5Gq1DBXbmQ+bUTL+Be5
+olJYO1IVP5CcTUmqUOw6yqm4idqoDdmRxwhglRIqv4OYOcU6JBlOqQHmYIKTiwgU
+I0y0lRiWQF+lPaa97HI0YrTcqYG0eLMCDKWnk1FBlIvOcrnrOS7/zzcYYoytYvNi
+//iavyQNMfHlhPUNNmE3TNQ6OmRkKI8wvoRkrvjfnwf04suIrOkd1ADdNqguWTUO
+pNE316lgmZpkfd4hYzlSGTCfjuhRqa1/a7eA8GNkKq03xuOI75rR15eZAgMBAAGj
+NTAzMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMBkGA1UdEQQSMBCCCGtyLmxvY2Fs
+hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQBC4Op5SaXqAdvZKjqO8cd8JpJlnGa/
+UnuQAsVgsQ2giBuZMIMIHzxawbEfCOHhkv1doLW9uEWyPQEGdQntoCHqc4Mb70Zd
+XBcepTlqUprpx/k6nw2io3JCUMmhbgfme63c5OiodxbBQCfWf6vIZjfOSPN0IYVr
+NY4kc0jcUEN0dsqjXEqKVbiDUcApKGGbe87KmSqi02UZHSoBPxm/Tr12n1Eu4AJR
+k7e3tJ3dslIYxYbQE6tUKiPWlP+rETcL7ran0m3YzEplMPvZdme9mu3w9Upsigx7
+lRCELEjwpBPZ5cUD04SkesoTGP6g8ZbmdpyjkI7lOjXvrBDim20EK47x
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDYjCCAkqgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwMzELMAkGA1UEBhMCR0Ix
+EDAOBgNVBAgMB0VuZ2xhbmQxEjAQBgNVBAoMCUFsaWNlIEx0ZDAeFw0xOTAzMjgx
+MjU5NTRaFw0zOTAzMjMxMjU5NTRaME0xCzAJBgNVBAYTAkdCMRAwDgYDVQQIDAdF
+bmdsYW5kMRIwEAYDVQQKDAlBbGljZSBMdGQxGDAWBgNVBAMMD0ludGVybWVkaWF0
+ZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMOH4k6f8wL5IruB
+m1BqQCE4ZQnDN8SX1ZwDp7G7tlycnJDB9PBkCziZcU11ezw6xdhT3J0jCEDwLrx2
+gsi+DWJwsC4CfcLdN1iuPeMRZQyC4GmQzPTJmQ8Kl/dQFZPV0wkd+JZNOfvpxpnD
+5WHkqg3DX6933/86raZ3NVfeVmTdHRdol+K8ve+1924IfrZNbZxatCX7AwEgyUFi
+qogXjcO+fqQed8VzHXGasqYMMLlMZMGLmusHlK6pDyscayubNOk1U9eHoRNYp6cb
+LChG8WnSpFv/LAB6QuyxvOwTK86R7k3J48ProPEFHkhUhHXxIHnR5l5+XLxcQSJ9
+xP7dFL8CAwEAAaNmMGQwHQYDVR0OBBYEFOaglkXoUdbRnwAXnDbbX5g62VXQMB8G
+A1UdIwQYMBaAFBFWdwWCxRarnrokwBx6MmPS324WMBIGA1UdEwEB/wQIMAYBAf8C
+AQAwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IBAQDeEA0klOFmdxHV
+jO0z2nXKE2jRvckoSKDGxj0L6gSxoUVRqVJMvlYmje8i0D9NyFCTlhRBX/24NCoZ
+2Oe4drzAiF+g97Ns7wIyYhAhcdJLn/820FZLsDMUbvW0BSsKsFckEABev3/miuJE
+O5/OzkU53V+7Nu4zT8CcI5cni2tn7Xq0p0L410lL6Lobkn7jON+BNhshK+H/Vcwj
+hf8L+ZxlDomXx7SfcIDc6AdFz2t4gu+6CyP/G/zeqUOPeh2WrqHg+caPQcq58Ol8
+p1r258mH8fxafu7Qcm2j23ZjW7xrDVC2XLWeXozJztlnODv73Jm686jHKflcfFTm
+6OgxKoIT
+-----END CERTIFICATE-----
diff --git a/modules/http/test_tls/test.crt b/modules/http/test_tls/test.crt
new file mode 100644
index 0000000..e9405f9
--- /dev/null
+++ b/modules/http/test_tls/test.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDVjCCAj6gAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCR0Ix
+EDAOBgNVBAgMB0VuZ2xhbmQxEjAQBgNVBAoMCUFsaWNlIEx0ZDEYMBYGA1UEAwwP
+SW50ZXJtZWRpYXRlIENBMB4XDTE5MDYxNDA5NTAzOVoXDTM5MDYwOTA5NTAzOVow
+WDELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu
+dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDERMA8GA1UEAwwIa3IubG9jYWwwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVh57hcYaSjQVQ+cUPD++o4ws0vI+S
+2LGQ4u+YzRvx2o3r85DFklfYTM2OICJKEyMNsUm6c45gc5Gq1DBXbmQ+bUTL+Be5
+olJYO1IVP5CcTUmqUOw6yqm4idqoDdmRxwhglRIqv4OYOcU6JBlOqQHmYIKTiwgU
+I0y0lRiWQF+lPaa97HI0YrTcqYG0eLMCDKWnk1FBlIvOcrnrOS7/zzcYYoytYvNi
+//iavyQNMfHlhPUNNmE3TNQ6OmRkKI8wvoRkrvjfnwf04suIrOkd1ADdNqguWTUO
+pNE316lgmZpkfd4hYzlSGTCfjuhRqa1/a7eA8GNkKq03xuOI75rR15eZAgMBAAGj
+NTAzMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMBkGA1UdEQQSMBCCCGtyLmxvY2Fs
+hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQBC4Op5SaXqAdvZKjqO8cd8JpJlnGa/
+UnuQAsVgsQ2giBuZMIMIHzxawbEfCOHhkv1doLW9uEWyPQEGdQntoCHqc4Mb70Zd
+XBcepTlqUprpx/k6nw2io3JCUMmhbgfme63c5OiodxbBQCfWf6vIZjfOSPN0IYVr
+NY4kc0jcUEN0dsqjXEqKVbiDUcApKGGbe87KmSqi02UZHSoBPxm/Tr12n1Eu4AJR
+k7e3tJ3dslIYxYbQE6tUKiPWlP+rETcL7ran0m3YzEplMPvZdme9mu3w9Upsigx7
+lRCELEjwpBPZ5cUD04SkesoTGP6g8ZbmdpyjkI7lOjXvrBDim20EK47x
+-----END CERTIFICATE-----
diff --git a/modules/http/test_tls/test.key b/modules/http/test_tls/test.key
new file mode 100644
index 0000000..1faa097
--- /dev/null
+++ b/modules/http/test_tls/test.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA1Yee4XGGko0FUPnFDw/vqOMLNLyPktixkOLvmM0b8dqN6/OQ
+xZJX2EzNjiAiShMjDbFJunOOYHORqtQwV25kPm1Ey/gXuaJSWDtSFT+QnE1JqlDs
+OsqpuInaqA3ZkccIYJUSKr+DmDnFOiQZTqkB5mCCk4sIFCNMtJUYlkBfpT2mvexy
+NGK03KmBtHizAgylp5NRQZSLznK56zku/883GGKMrWLzYv/4mr8kDTHx5YT1DTZh
+N0zUOjpkZCiPML6EZK74358H9OLLiKzpHdQA3TaoLlk1DqTRN9epYJmaZH3eIWM5
+Uhkwn47oUamtf2u3gPBjZCqtN8bjiO+a0deXmQIDAQABAoIBAQCBnoUk50xAlBhh
+Em27+fmKtOBtj/U7uAz6HbhCMmg/RWOXktAUDwUCSYUSPJF0E+/YdQGDjHgmNqF7
+aLk7qchyWNRFWQHV7yI7ay8ltONs7kHEgMEV40Zpvk0cbOPg6Ug9kOBpUL5qXs9J
+vvYZ2OBNX9KEDAbIarE6gbNeKg+ldw5q8kCpDWpv+N7zmXq/9GDWPNBG/5lvGYrV
+MgBm6X4OdaYBD234MV5fNLtqiZQuM3JquEf4gFCCeBpA1MJYcodC+VcXNeZotziZ
+xssbGVsevPpLUkFNDV0s5UFYzEKgoCPNS+usNnwD5r6rFof7vEt8UHfKI2Dz6C9R
+q9xMFt4BAoGBAOzdsCQbveuGbae1HD616TliwgrGjaOVh8sT3r79YRLwekhWgqaP
+vbH0m6sEHKi0Sltj8jWd/ktO0ttfaMy45/DQRNjtPOwhr4kT+bmuyrMCfVOWsW/L
+bRrCU348OKr+VyhEI4YEFsaN1jpiwaQmaqcZFbGKJM84wW1OccUY2d7BAoGBAObH
+WPMKU1YwJmeMs38/MMUwWaw7BrLLk2zRsrOLX0ongp4+TGgI0iaFr8gwh3gAUc6W
+VjPyQtlWh2PLA9VabnSAmPu/NNq7X8fbpKFaBFdlkcFq7DpA8RuBN/b6+p4LD2Tb
+2q92VF4szrcflCnXnZrNxUqjY7NPxARw3y6vgUbZAoGAKImIK6XTywsmmR0VyGW5
+lGiibNWuR+C/bLHp3SXgBy3Av8COe5L+FAaY3ZvGi9jPIPTp7uMrMhg7Xe/mL6M1
+jrEWF0oCsybQs9UHWA/iAODcMgIIO+nEsl+vilskF5+PqwR+T+FDRJfhofxkx4ML
+na1dWRUbV5uO/vX94o1uPAECgYBTwHTffxfPZ5oIal+aBmzEo09n2eQMbyUJkPCx
+iBsE5mHY2/MOrmTV5h5tIG+JdVQ7DQQrxffMuEJaTQsPGsqLLUBX3IRp/SY9edC9
+XdXFge7rqsogOgFGYhbVYzAguxLTH5a1ptPneYtrmeJDbSSdUaAP/kvof0I7+lqE
+rtzTwQKBgQDTlchGtbuVAQzWuaN86UyXU43Ug9YybyLhanh8ZLCoUxsZVf2D2zVb
+TvqzKCx2qP+f3c6ydkg7pRSY0R80bxshhv1qUAvEqC74JgLBDlD7R/rQlj17MtNl
+G3TZIcskdnaHY5zydtvxXJxNxCCHJyBYqWN4L6NzJ+hp7lbP6hQICQ==
+-----END RSA PRIVATE KEY-----
diff --git a/modules/http/test_tls/tls.test.lua b/modules/http/test_tls/tls.test.lua
new file mode 100644
index 0000000..7d5c437
--- /dev/null
+++ b/modules/http/test_tls/tls.test.lua
@@ -0,0 +1,193 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- check prerequisites
+local has_http = pcall(require, 'kres_modules.http') and pcall(require, 'http.request')
+if not has_http then
+ -- skipping http module test because its not installed
+ os.exit(77)
+else
+ local request = require('http.request')
+ local openssl_ctx = require('openssl.ssl.context')
+
+ local function setup_module(desc, config)
+ if http then
+ modules.unload('http')
+ end
+ modules.load('http')
+ same(http.config(config), nil, desc .. ' can be configured')
+
+ local bound
+ for _ = 1,1000 do
+ bound, _err = pcall(net.listen, '127.0.0.1', math.random(1025,65535), { kind = 'webmgmt' })
+ if bound then
+ break
+ end
+ end
+ assert(bound, 'unable to bind a port for HTTP module (1000 attempts)')
+
+ local server_fd = next(http.servers)
+ assert(server_fd)
+ local server = http.servers[server_fd].server
+ ok(server ~= nil, 'creates server instance')
+ _, host, port = server:localname()
+ ok(host and port, 'binds to an interface')
+ return host, port
+ end
+
+ local function http_get(uri)
+ -- disable certificate verification in this test
+ local req = request.new_from_uri(uri)
+ local idxstart = string.find(uri, 'https://')
+ if idxstart == 1 then
+ req.ctx = openssl_ctx.new()
+ assert(req.ctx, 'OpenSSL cert verification must be disabled')
+ req.ctx:setVerify(openssl_ctx.VERIFY_NONE)
+ end
+
+ local headers = assert(req:go(16))
+ return tonumber(headers:get(':status'))
+ end
+
+ -- test whether http interface responds and binds
+ local function check_protocol(uri, description, ok_expected)
+ if ok_expected then
+ local code = http_get(uri)
+ same(code, 200, description)
+ else
+ boom(http_get, {uri}, description)
+ end
+ end
+
+ local function test_defaults()
+ local host, port = setup_module('HTTP module default config', nil)
+
+ local uri = string.format('http://%s:%d', host, port)
+ check_protocol(uri, 'HTTP is enabled by default', true)
+ uri = string.format('https://%s:%d', host, port)
+ check_protocol(uri, 'HTTPS is enabled by default', true)
+
+ modules.unload('http')
+ uri = string.format('http://%s:%d', host, port)
+ check_protocol(uri, 'HTTP stops working after module unload', false)
+ uri = string.format('https://%s:%d', host, port)
+ check_protocol(uri, 'HTTPS stops working after module unload', false)
+
+ end
+
+ local function test_http_only()
+ local desc = 'HTTP-only config'
+ local host, port = setup_module(desc,
+ {
+ tls = false,
+ })
+
+ local uri = string.format('http://%s:%d', host, port)
+ check_protocol(uri, 'HTTP works in ' .. desc, true)
+ uri = string.format('https://%s:%d', host, port)
+ check_protocol(uri, 'HTTPS does not work in ' .. desc, false)
+ end
+
+ local function test_https_only()
+ local desc = 'HTTPS-only config'
+ local host, port = setup_module(desc,
+ {
+ tls = true,
+ })
+
+ local uri = string.format('http://%s:%d', host, port)
+ check_protocol(uri, 'HTTP does not work in ' .. desc, false)
+ uri = string.format('https://%s:%d', host, port)
+ check_protocol(uri, 'HTTPS works in ' .. desc, true)
+ end
+
+ local function test_custom_cert()
+ desc = 'config with custom certificate'
+ local host, port = setup_module(desc, {{
+ cert = 'test.crt',
+ key = 'test.key'
+ }})
+
+ uri = string.format('https://%s:%d', host, port)
+ check_protocol(uri, 'HTTPS works for ' .. desc, true)
+ end
+
+ local function test_nonexistent_cert()
+ desc = 'config with non-existing certificate file'
+ boom(http.config, {{
+ cert = '/tmp/surely_nonexistent_cert_1532432095',
+ key = 'test.key'
+ }}, desc)
+ end
+
+ local function test_nonexistent_key()
+ desc = 'config with non-existing key file'
+ boom(http.config, {{
+ cert = 'test.crt',
+ key = '/tmp/surely_nonexistent_cert_1532432095'
+ }}, desc)
+ end
+
+ local function test_missing_key_param()
+ desc = 'config with missing key= param'
+ boom(http.config, {{
+ cert = 'test.crt'
+ }}, desc)
+ end
+
+ local function test_broken_cert()
+ desc = 'config with broken file in cert= param'
+ boom(http.config, {{
+ cert = 'broken.crt',
+ key = 'test.key'
+ }}, desc)
+ end
+
+ local function test_broken_key()
+ desc = 'config with broken file in key= param'
+ boom(http.config, {{
+ cert = 'test.crt',
+ key = 'broken.key'
+ }}, desc)
+ end
+
+ local function test_certificate_chain()
+ local desc = 'config with certificate chain (with intermediate CA cert)'
+ local host, port = setup_module(desc,
+ {
+ tls = true,
+ cert = 'chain.crt',
+ key = 'test.key',
+ })
+ local uri = string.format('https://%s:%d', host, port)
+ local req = request.new_from_uri(uri)
+ req.ctx = openssl_ctx.new()
+
+ if not req.ctx.setCertificateChain then
+ pass(string.format('SKIP (luaossl <= 20181207) - %s', desc))
+ else
+ local store = req.ctx:getStore()
+ store:add('ca.crt')
+ req.ctx:setVerify(openssl_ctx.VERIFY_PEER)
+
+ local headers = assert(req:go(16))
+ local code = tonumber(headers:get(':status'))
+ same(code, 200, desc)
+ end
+ end
+
+
+ -- plan tests
+ local tests = {
+ test_defaults,
+ test_http_only,
+ test_https_only,
+ test_custom_cert,
+ test_nonexistent_cert,
+ test_nonexistent_key,
+ test_missing_key_param,
+ test_broken_cert,
+ test_broken_key,
+ test_certificate_chain,
+ }
+
+ return tests
+end
diff --git a/modules/http/trace.rst b/modules/http/trace.rst
new file mode 100644
index 0000000..de6950c
--- /dev/null
+++ b/modules/http/trace.rst
@@ -0,0 +1,43 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-http-trace:
+
+Debugging a single request
+==========================
+
+Using query policies
+--------------------
+
+Query policies :any:`policy.DEBUG_ALWAYS`, :any:`policy.DEBUG_CACHE_MISS` or
+:any:`policy.DEBUG_IF` can be used to enable debug-level logging for selected
+subdomains or queries matching specific conditions. Please refer to
+:ref:`mod-policy-logging` for more information.
+
+Using HTTP module
+-----------------
+
+The :ref:`http module <mod-http>` provides ``/trace`` endpoint which allows to trace various
+aspects of the request execution. The basic mode allows you to resolve a query
+and trace debug-level logs for it (and messages received):
+
+.. code-block:: bash
+
+ $ curl https://localhost:8453/trace/e.root-servers.net
+ [ 8138] [iter] 'e.root-servers.net.' type 'A' created outbound query, parent id 0
+ [ 8138] [ rc ] => rank: 020, lowest 020, e.root-servers.net. A
+ [ 8138] [ rc ] => satisfied from cache
+ [ 8138] [iter] <= answer received:
+ ;; ->>HEADER<<- opcode: QUERY; status: NOERROR; id: 8138
+ ;; Flags: qr aa QUERY: 1; ANSWER: 0; AUTHORITY: 0; ADDITIONAL: 0
+
+ ;; QUESTION SECTION
+ e.root-servers.net. A
+
+ ;; ANSWER SECTION
+ e.root-servers.net. 3556353 A 192.203.230.10
+
+ [ 8138] [iter] <= rcode: NOERROR
+ [ 8138] [resl] finished: 4, queries: 1, mempool: 81952 B
+
+See chapter about :ref:`mod-http` for further instructions how to load ``webmgmt``
+endpoint into HTTP module, it is a prerequisite for using ``/trace``.
diff --git a/modules/meson.build b/modules/meson.build
new file mode 100644
index 0000000..c5360c8
--- /dev/null
+++ b/modules/meson.build
@@ -0,0 +1,59 @@
+# modules
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+lua_mod_src = [ # add lua modules without separate meson.build
+ files('detect_time_jump/detect_time_jump.lua'),
+ files('detect_time_skew/detect_time_skew.lua'),
+ files('dns64/dns64.lua'),
+ files('etcd/etcd.lua'),
+ files('graphite/graphite.lua'),
+ files('predict/predict.lua'),
+ files('prefill/prefill.lua'),
+ files('priming/priming.lua'),
+ files('rebinding/rebinding.lua'),
+ files('renumber/renumber.lua'),
+ files('serve_stale/serve_stale.lua'),
+ files('ta_sentinel/ta_sentinel.lua'),
+ files('ta_signal_query/ta_signal_query.lua'),
+ files('watchdog/watchdog.lua'),
+ files('workarounds/workarounds.lua'),
+]
+
+# When adding tests, prefer to use module's meson.build (if it exists).
+config_tests += [
+ ['dns64', files('dns64/dns64.test.lua')],
+ ['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', meson.current_source_dir() / 'rebinding' / 'test.integr'],
+ ['serve_stale', meson.current_source_dir() / 'serve_stale' / 'test.integr'],
+]
+
+
+# handle more complex C/LUA modules separately
+subdir('bogus_log')
+# cookies module is not currently used
+subdir('daf')
+subdir('dnstap')
+subdir('edns_keepalive')
+subdir('experimental_dot_auth')
+subdir('extended_error')
+subdir('hints')
+subdir('http')
+subdir('nsid')
+subdir('policy')
+subdir('refuse_nord')
+subdir('stats')
+subdir('ta_update')
+subdir('view')
+
+# install lua modules
+foreach mod : lua_mod_src
+ install_data(
+ mod,
+ install_dir: modules_dir,
+ )
+endforeach
diff --git a/modules/nsid/.packaging/test.config b/modules/nsid/.packaging/test.config
new file mode 100644
index 0000000..de54cce
--- /dev/null
+++ b/modules/nsid/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('nsid')
+assert(nsid)
+quit()
diff --git a/modules/nsid/README.rst b/modules/nsid/README.rst
new file mode 100644
index 0000000..3d0bf4e
--- /dev/null
+++ b/modules/nsid/README.rst
@@ -0,0 +1,35 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-nsid:
+
+Name Server Identifier (NSID)
+=============================
+
+Module ``nsid`` provides server-side support for :rfc:`5001`
+which allows DNS clients to request resolver to send back its NSID
+along with the reply to a DNS request.
+This is useful for debugging larger resolver farms
+(e.g. when using :ref:`systemd-multiple-instances`, anycast or load balancers).
+
+NSID value can be configured in the resolver's configuration file:
+
+.. code-block:: lua
+
+ modules.load('nsid')
+ nsid.name('instance 1')
+
+.. tip:: When dealing with Knot Resolver running in `multiple instances`
+ managed with systemd see :ref:`instance-specific-configuration`.
+
+You can also obtain configured NSID value:
+
+.. code-block:: lua
+
+ > nsid.name()
+ 'instance 1'
+
+The module can be disabled at run-time:
+
+.. code-block:: lua
+
+ modules.unload('nsid')
diff --git a/modules/nsid/meson.build b/modules/nsid/meson.build
new file mode 100644
index 0000000..b0fcd9e
--- /dev/null
+++ b/modules/nsid/meson.build
@@ -0,0 +1,24 @@
+# C module: nsid
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+nsid_src = files([
+ 'nsid.c',
+])
+c_src_lint += nsid_src
+
+nsid_mod = shared_module(
+ 'nsid',
+ nsid_src,
+ dependencies: [
+ libknot,
+ luajit_inc,
+ ],
+ include_directories: mod_inc_dir,
+ name_prefix: '',
+ install: true,
+ install_dir: modules_dir,
+)
+
+config_tests += [
+ ['nsid', files('nsid.test.lua')],
+]
diff --git a/modules/nsid/nsid.c b/modules/nsid/nsid.c
new file mode 100644
index 0000000..a67dcdb
--- /dev/null
+++ b/modules/nsid/nsid.c
@@ -0,0 +1,114 @@
+/* Copyright (C) Knot Resolver contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * This module provides NSID support according to RFC 5001. */
+
+#include <libknot/packet/pkt.h>
+#include <contrib/cleanup.h>
+#include <ccan/json/json.h>
+#include <lauxlib.h>
+
+#include "daemon/engine.h"
+#include "lib/layer.h"
+
+struct nsid_config {
+ uint8_t *local_nsid;
+ size_t local_nsid_len;
+};
+
+static int nsid_finalize(kr_layer_t *ctx) {
+ const struct kr_module *module = ctx->api->data;
+ const struct nsid_config *config = module->data;
+ struct kr_request *req = ctx->req;
+
+ /* no local NSID configured, do nothing */
+ if (config->local_nsid == NULL)
+ return ctx->state;
+
+ const knot_rrset_t *src_opt = req->qsource.packet->opt_rr;
+ /* no EDNS in request, do nothing */
+ if (src_opt == NULL)
+ return ctx->state;
+
+ const uint8_t *req_nsid = knot_edns_get_option(src_opt, KNOT_EDNS_OPTION_NSID, NULL);
+ /* NSID option must be explicitly requested */
+ if (req_nsid == NULL)
+ return ctx->state;
+
+ /* Check violation of https://tools.ietf.org/html/rfc5001#section-2.1:
+ * The resolver MUST NOT include any NSID payload data in the query */
+ if (knot_edns_opt_get_length(req_nsid) != 0)
+ kr_log_debug(NSID, "[%05u. ] FORMERR: NSID option in query "
+ "must not contain payload, continuing\n", req->uid);
+ /* FIXME: actually change RCODE in answer to FORMERR? */
+
+ /* Sanity check, answer should have EDNS as well but who knows ... */
+ if (kr_fails_assert(req->answer->opt_rr))
+ return ctx->state;
+
+ if (knot_edns_add_option(req->answer->opt_rr, KNOT_EDNS_OPTION_NSID,
+ config->local_nsid_len, config->local_nsid,
+ &req->pool) != KNOT_EOK) {
+ /* something went wrong and there is no way to salvage content of OPT RRset */
+ kr_log_debug(NSID, "[%05u. ] unable to add NSID option\n", req->uid);
+ knot_rrset_clear(req->answer->opt_rr, &req->pool);
+ }
+
+ return ctx->state;
+}
+
+static char* nsid_name(void *env, struct kr_module *module, const char *args)
+{
+ struct engine *engine = env;
+ struct nsid_config *config = module->data;
+ if (args) { /* set */
+ /* API is not binary safe, we need to fix this one day */
+ uint8_t *arg_copy = (uint8_t *)strdup(args);
+ if (arg_copy == NULL)
+ luaL_error(engine->L, "[nsid] error while allocating new NSID value\n");
+ free(config->local_nsid);
+ config->local_nsid = arg_copy;
+ config->local_nsid_len = strlen(args);
+ }
+
+ /* get */
+ if (config->local_nsid != NULL)
+ return json_encode_string((char *)config->local_nsid);
+ else
+ return NULL;
+}
+
+KR_EXPORT
+int nsid_init(struct kr_module *module) {
+ static kr_layer_api_t layer = {
+ .answer_finalize = &nsid_finalize,
+ };
+ layer.data = module;
+ module->layer = &layer;
+
+ static const struct kr_prop props[] = {
+ { &nsid_name, "name", "Get or set local NSID value" },
+ { NULL, NULL, NULL }
+ };
+ module->props = props;
+
+ struct nsid_config *config = calloc(1, sizeof(struct nsid_config));
+ if (config == NULL)
+ return kr_error(ENOMEM);
+
+ module->data = config;
+ return kr_ok();
+}
+
+KR_EXPORT
+int nsid_deinit(struct kr_module *module) {
+ struct nsid_config *config = module->data;
+ if (config != NULL) {
+ free(config->local_nsid);
+ free(config);
+ module->data = NULL;
+ }
+ return kr_ok();
+}
+
+KR_MODULE_EXPORT(nsid)
diff --git a/modules/nsid/nsid.test.lua b/modules/nsid/nsid.test.lua
new file mode 100644
index 0000000..8e5d4f9
--- /dev/null
+++ b/modules/nsid/nsid.test.lua
@@ -0,0 +1,22 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- disable networking so we can get SERVFAIL immediately
+net.ipv4 = false
+net.ipv6 = false
+
+-- test for nsid.name() interface
+local function test_nsid_name()
+ if nsid then
+ modules.unload('nsid')
+ end
+ modules.load('nsid')
+ same(nsid.name(), nil, 'NSID modes not provide default NSID value')
+ same(nsid.name('123456'), '123456', 'NSID value can be changed')
+ same(nsid.name(), '123456', 'NSID module remembers configured NSID value')
+ modules.unload('nsid')
+ modules.load('nsid')
+ same(nsid.name(), nil, 'NSID module reload removes configured value')
+end
+
+return {
+ test_nsid_name,
+}
diff --git a/modules/policy/.packaging/test.config b/modules/policy/.packaging/test.config
new file mode 100644
index 0000000..60c9ddc
--- /dev/null
+++ b/modules/policy/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('policy')
+assert(policy)
+quit()
diff --git a/modules/policy/README.rst b/modules/policy/README.rst
new file mode 100644
index 0000000..202aaba
--- /dev/null
+++ b/modules/policy/README.rst
@@ -0,0 +1,774 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. default-domain:: py
+.. module:: policy
+
+.. _mod-policy:
+
+
+Query policies
+==============
+
+This module can block, rewrite, or alter inbound queries based on user-defined policies. It does not affect queries generated by the resolver itself, e.g. when following CNAME chains etc.
+
+Each policy *rule* has two parts: a *filter* and an *action*. A *filter* selects which queries will be affected by the policy, and *action* which modifies queries matching the associated filter.
+
+Typically a rule is defined as follows: ``filter(action(action parameters), filter parameters)``. For example, a filter can be ``suffix`` which matches queries whose suffix part is in specified set, and one of possible actions is :any:`policy.DENY`, which denies resolution. These are combined together into ``policy.suffix(policy.DENY, {todname('badguy.example.')})``. The rule is effective when it is added into rule table using ``policy.add()``, please see examples below.
+
+This module is enabled by default because it implements mandatory :rfc:`6761` logic.
+When no rule applies to a query, built-in rules for `special-use <https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml>`_ and `locally-served <http://www.iana.org/assignments/locally-served-dns-zones>`_ domain names are applied.
+These rules can be overridden by action :any:`policy.PASS`. For debugging purposes you can also add ``modules.unload('policy')`` to your config to unload the module.
+
+
+Filters
+-------
+A *filter* selects which queries will be affected by specified Actions_. There are several policy filters available in the ``policy.`` table:
+
+.. function:: all(action)
+
+ Always applies the action.
+
+.. function:: pattern(action, pattern)
+
+ Applies the action if query name matches a `Lua regular expression <http://lua-users.org/wiki/PatternsTutorial>`_.
+
+.. function:: suffix(action, suffix_table)
+
+ Applies the action if query name suffix matches one of suffixes in the table (useful for "is domain in zone" rules).
+
+ .. code-block:: lua
+
+ policy.add(policy.suffix(policy.DENY, policy.todnames({'example.com', 'example.net'})))
+
+.. note:: For speed this filter requires domain names in DNS wire format, not textual representation, so each label in the name must be prefixed with its length. Always use convenience function :func:`policy.todnames` for automatic conversion from strings! For example:
+
+.. _IDN:
+
+.. note:: Non-ASCII is not supported.
+
+ Knot Resolver does not provide any convenience support for IDN.
+ Therefore everywhere (all configuration, logs, RPZ files) you need to deal with the
+ `xn\-\- forms <https://en.wikipedia.org/wiki/Internationalized_domain_name#Example_of_IDNA_encoding>`_
+ of domain name labels, instead of directly using unicode characters.
+
+.. function:: domains(action, domain_table)
+
+ Like :func:`policy.suffix` match, but the queried name must match exactly, not just its suffix.
+
+.. function:: suffix_common(action, suffix_table[, common_suffix])
+
+ :param action: action if the pattern matches query name
+ :param suffix_table: table of valid suffixes
+ :param common_suffix: common suffix of entries in suffix_table
+
+ Like :func:`policy.suffix` match, but you can also provide a common suffix of all matches for faster processing (nil otherwise).
+ This function is faster for small suffix tables (in the order of "hundreds").
+
+.. :noindex: function:: rpz(default_action, path, [watch])
+
+ Implements a subset of `Response Policy Zone` (RPZ_) stored in zonefile format. See below for details: :func:`policy.rpz`.
+
+It is also possible to define custom filter function with any name.
+
+.. function:: custom_filter(state, query)
+
+ :param state: Request processing state :c:type:`kr_layer_state`, typically not used by filter function.
+ :param query: Incoming DNS query as :c:type:`kr_query` structure.
+ :return: An `action <#actions>`_ function or ``nil`` if filter did not match.
+
+ Typically filter function is generated by another function, which allows easy parametrization - this technique is called `closure <https://www.lua.org/pil/6.1.html>`_. An practical example of such filter generator is:
+
+.. code-block:: lua
+
+ function match_query_type(action, target_qtype)
+ return function (state, query)
+ if query.stype == target_qtype then
+ -- filter matched the query, return action function
+ return action
+ else
+ -- filter did not match, continue with next filter
+ return nil
+ end
+ end
+ end
+
+This custom filter can be used as any other built-in filter.
+For example this applies our custom filter and executes action :any:`policy.DENY` on all queries of type `HINFO`:
+
+.. code-block:: lua
+
+ -- custom filter which matches HINFO queries, action is policy.DENY
+ policy.add(match_query_type(policy.DENY, kres.type.HINFO))
+
+
+.. _mod-policy-actions:
+
+Actions
+-------
+An *action* is a function which modifies DNS request, and is either of type *chain* or *non-chain*:
+
+ * `Non-chain actions`_ modify state of the request and stop rule processing. An example of such action is :ref:`forwarding`.
+ * `Chain actions`_ modify state of the request and allow other rules to evaluate and act on the same request. One such example is :func:`policy.MIRROR`.
+
+Non-chain actions
+^^^^^^^^^^^^^^^^^
+
+Following actions stop the policy matching on the query, i.e. other rules are not evaluated once rule with following actions matches:
+
+.. py:attribute:: PASS
+
+ Let the query pass through; it's useful to make exceptions before wider rules. For example:
+
+ More specific whitelist rule must precede generic blacklist rule:
+
+ .. code-block:: lua
+
+ -- 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
+
+ Deny existence of names matching filter, i.e. reply NXDOMAIN authoritatively.
+
+.. function:: DENY_MSG(message, [extended_error=kres.extended_error.BLOCKED])
+
+ Deny existence of a given domain and add explanatory message. NXDOMAIN reply
+ contains an additional explanatory message as TXT record in the additional
+ section.
+
+ You may override the extended DNS error to provide the user with more
+ information. By default, ``BLOCKED`` is returned to indicate the domain is
+ blocked due to the internal policy of the operator. Other suitable error
+ codes are ``CENSORED`` (for externally imposed policy reasons) or
+ ``FILTERED`` (for blocking requested by the client). For more information,
+ please refer to :rfc:`8914`.
+
+.. py:attribute:: DROP
+
+ Terminate query resolution and return SERVFAIL to the requestor.
+
+.. py:attribute:: REFUSE
+
+ Terminate query resolution and return REFUSED to the requestor.
+
+.. py:attribute:: NO_ANSWER
+
+ Terminate query resolution and do not return any answer to the requestor.
+
+ .. warning:: During normal operation, an answer should always be returned.
+ Deliberate query drops are indistinguishable from packet loss and may
+ cause problems as described in :rfc:`8906`. Only use :any:`NO_ANSWER`
+ on very specific occasions, e.g. as a defense mechanism during an attack,
+ and prefer other actions (e.g. :any:`DROP` or :any:`REFUSE`) for normal
+ operation.
+
+.. py:attribute:: TC
+
+ Force requestor to use TCP. It sets truncated bit (*TC*) in response to true if the request came through UDP, which will force standard-compliant clients to retry the request over TCP.
+
+.. function:: REROUTE({subnet = target, ...})
+
+ Reroute IP addresses in response matching given subnet to given target, e.g. ``{['192.0.2.0/24'] = '127.0.0.0'}`` will rewrite '192.0.2.55' to '127.0.0.55', see :ref:`renumber module <mod-renumber>` for more information. See :func:`policy.add` and do not forget to specify that this is *postrule*. Quick example:
+
+ .. 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.all(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 <https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4>`_.
+ * 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. Wire format for other record types can be generated with :func:`kres.parse_rdata`.
+ * ttl
+ - TTL in seconds. Default: 1 second.
+ * nodata
+ - If type requested by client is not configured in this policy:
+
+ - ``true``: Return empty answer (`NODATA`).
+ - ``false``: Ignore this policy and continue processing other rules.
+
+ Default: ``false``.
+
+ .. code-block:: lua
+
+ -- policy to change IPv4 address and TTL for example.com
+ policy.add(
+ policy.domains(
+ 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.domains(
+ policy.ANSWER(
+ { [kres.type.TXT] = { rdata={'\005first', '\006second'}, ttl=5 } }
+ ), { todname('example.net') }))
+
+
+ .. function:: kres.parse_rdata({str, ...})
+
+ Parse string representation of RTYPE and RDATA into RDATA wire format. Expects
+ a table of string(s) and returns a table of wire data.
+
+ .. code-block:: lua
+
+ -- create wire format RDATA that can be passed to policy.ANSWER
+ kres.parse_rdata({'SVCB 1 resolver.example. alpn=dot'})
+ kres.parse_rdata({
+ 'SVCB 1 resolver.example. alpn=dot ipv4hint=192.0.2.1 ipv6hint=2001:db8::1',
+ 'SVCB 2 resolver.example. mandatory=key65380 alpn=h2 key65380=/dns-query{?dns}',
+ })
+
+More complex non-chain actions are described in their own chapters, namely:
+
+ * :ref:`forwarding`
+ * `Response Policy Zones`_
+
+Chain actions
+^^^^^^^^^^^^^
+
+Following actions act on request and then processing continue until first non-chain action (specified in the previous section) is triggered:
+
+.. function:: MIRROR(ip_address)
+
+ Send copy of incoming DNS queries to a given IP address using DNS-over-UDP and continue resolving them as usual. This is useful for sanity testing new versions of DNS resolvers.
+
+ .. code-block:: lua
+
+ policy.add(policy.all(policy.MIRROR('127.0.0.2')))
+
+.. function:: FLAGS(set, clear)
+
+ Set and/or clear some flags for the query. There can be multiple flags to set/clear. You can just pass a single flag name (string) or a set of names. Flag names correspond to :c:type:`kr_qflags` structure. Use only if you know what you are doing.
+
+
+.. _mod-policy-logging:
+
+Actions for extra logging
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+These are also "chain" actions, i.e. they don't stop processing the policy rule list.
+Similarly to other actions, they apply during whole processing of the client's request,
+i.e. including any sub-queries.
+
+The log lines from these policy actions are tagged by extra ``[reqdbg]`` prefix,
+and they are produced regardless of your :func:`log_level()` setting.
+They are marked as ``debug`` level, so e.g. with journalctl command you can use ``-p info`` to skip them.
+
+.. warning:: Beware of producing too much logs.
+
+ These actions are not suitable for use on a large fraction of resolver's requests.
+ The extra logs have significant performance impact and might also overload your logging system
+ (or get rate-limited by it).
+ You can use `Filters`_ to further limit on which requests this happens.
+
+.. py:attribute:: DEBUG_ALWAYS
+
+ Print debug-level logging for this request.
+ That also includes messages from client (:any:`REQTRACE`), upstream servers (:any:`QTRACE`), and stats about interesting records at the end.
+
+ .. code-block:: lua
+
+ -- debug requests that ask for flaky.example.net or below
+ policy.add(policy.suffix(policy.DEBUG_ALWAYS,
+ policy.todnames({'flaky.example.net'})))
+
+.. py:attribute:: DEBUG_CACHE_MISS
+
+ Same as :any:`DEBUG_ALWAYS` but only if the request required information which was not available locally, i.e. requests which forced resolver to ask upstream server(s).
+ Intended usage is for debugging problems with remote servers.
+
+.. py:function:: DEBUG_IF(test_function)
+
+ :param test_function: Function with single argument of type :c:type:`kr_request` which returns ``true`` if debug logs for that request should be generated and ``false`` otherwise.
+
+ Same as :any:`DEBUG_ALWAYS` but only logs if the test_function says so.
+
+ .. note:: ``test_function`` is evaluated only when request is finished.
+ As a result all debug logs this request must be collected,
+ and at the end they get either printed or thrown away.
+
+ Example usage which gathers verbose logs for all requests in subtree ``dnssec-failed.org.`` and prints debug logs for those finishing in a different state than ``kres.DONE`` (most importantly ``kres.FAIL``, see :c:type:`kr_layer_state`).
+
+ .. code-block:: lua
+
+ policy.add(policy.suffix(
+ policy.DEBUG_IF(function(req)
+ return (req.state ~= kres.DONE)
+ end),
+ policy.todnames({'dnssec-failed.org.'})))
+
+.. py:attribute:: QTRACE
+
+ Pretty-print DNS responses from upstream servers (or cache) into logs.
+ It's useful for debugging weird DNS servers.
+
+ If you do not use ``QTRACE`` in combination with ``DEBUG*``,
+ you additionally need either ``log_groups({'iterat'})`` (possibly with other groups)
+ or ``log_level('debug')`` to see the output in logs.
+
+.. py:attribute:: REQTRACE
+
+ Pretty-print DNS requests from clients into the verbose log. It's useful for debugging weird DNS clients.
+ It makes most sense together with :ref:`mod-view` (enabling per-client)
+ and probably with verbose logging those request (e.g. use :any:`DEBUG_ALWAYS` instead).
+
+.. py:attribute:: IPTRACE
+
+ Log how the request arrived.
+ Most notably, this includes the client's IP address, so beware of privacy implications.
+
+ .. code-block:: lua
+
+ -- example usage in configuration
+ policy.add(policy.all(policy.IPTRACE))
+ -- you might want to combine it with some other logs, e.g.
+ policy.add(policy.all(policy.DEBUG_ALWAYS))
+
+ .. code-block:: text
+
+ -- example log lines from IPTRACE:
+ [reqdbg][policy][57517.00] request packet arrived from ::1#37931 to ::1#00853 (TCP + TLS)
+ [reqdbg][policy][65538.00] request packet arrived internally
+
+
+Custom actions
+^^^^^^^^^^^^^^
+
+.. function:: custom_action(state, request)
+
+ :param state: Request processing state :c:type:`kr_layer_state`.
+ :param request: Current DNS request as :c:type:`kr_request` structure.
+ :return: Returning a new :c:type:`kr_layer_state` prevents evaluating other policy rules. Returning ``nil`` creates a `chain action <#actions>`_ and allows to continue evaluating other rules.
+
+ This is real example of an action function:
+
+.. code-block:: lua
+
+ -- 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``:
+
+.. code-block:: lua
+
+ policy.add(policy.suffix(fake_A_record, policy.todnames({'example.net'})))
+
+The action function can implement arbitrary logic so it is possible to implement complex heuristics, e.g. to deflect `Slow drip DNS attacks <https://secure64.com/water-torture-slow-drip-dns-ddos-attack>`_ or gray-list resolution of misbehaving zones.
+
+.. warning:: The policy module currently only looks at whole DNS requests. The rules won't be re-applied e.g. when following CNAMEs.
+
+.. _forwarding:
+
+Forwarding
+----------
+
+Forwarding action alters behavior for cache-miss events. If an information is missing in the local cache the resolver will *forward* the query to *another DNS resolver* for resolution (instead of contacting authoritative servers directly). DNS answers from the remote resolver are then processed locally and sent back to the original client.
+
+Actions :func:`policy.FORWARD`, :func:`policy.TLS_FORWARD` and :func:`policy.STUB` accept up to four IP addresses at once and the resolver will automatically select IP address which statistically responds the fastest.
+
+.. function:: FORWARD(ip_address)
+ FORWARD({ ip_address, [ip_address, ...] })
+
+ Forward cache-miss queries to specified IP addresses (without encryption), DNSSEC validate received answers and cache them. Target IP addresses are expected to be DNS resolvers.
+
+ .. 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'})))
+
+ A variant which uses encrypted DNS-over-TLS transport is called :func:`policy.TLS_FORWARD`, please see section :ref:`tls-forwarding`.
+
+.. function:: STUB(ip_address)
+ STUB({ ip_address, [ip_address, ...] })
+
+ Similar to :func:`policy.FORWARD` but *without* attempting DNSSEC validation.
+ Each request may be either answered from cache or simply sent to one of the IPs with proxying back the answer.
+
+ This mode does not support encryption and should be used only for `Replacing part of the DNS tree`_.
+ Use :func:`policy.FORWARD` mode if possible.
+
+ .. 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')}))
+
+.. note:: By default, forwarding targets must support
+ `EDNS <https://en.wikipedia.org/wiki/Extension_mechanisms_for_DNS>`_ and
+ `0x20 randomization <https://tools.ietf.org/html/draft-vixie-dnsext-dns0x20-00>`_.
+ See example in `Replacing part of the DNS tree`_.
+
+.. warning::
+ Limiting forwarding actions by filters (e.g. :func:`policy.suffix`) may have unexpected consequences.
+ Notably, forwarders can inject *any* records into your cache
+ even if you "restrict" them to an insignificant DNS subtree --
+ except in cases where DNSSEC validation applies, of course.
+
+ The behavior is probably best understood through the fact
+ that filters and actions are completely decoupled.
+ The forwarding actions have no clue about why they were executed,
+ e.g. that the user wanted to restrict the forwarder only to some subtree.
+ The action just selects some set of forwarders to process this whole request from the client,
+ and during that processing it might need some other "sub-queries" (e.g. for validation).
+ Some of those might not've passed the intended filter,
+ but policy rule-set only applies once per client's request.
+
+.. _tls-forwarding:
+
+Forwarding over TLS protocol (DNS-over-TLS)
+-------------------------------------------
+.. function:: TLS_FORWARD( { {ip_address, authentication}, [...] } )
+
+ Same as :func:`policy.FORWARD` but send query over DNS-over-TLS protocol (encrypted).
+ Each target IP address needs explicit configuration how to validate
+ TLS certificate so each IP address is configured by pair:
+ ``{ip_address, authentication}``. See sections below for more details.
+
+
+Policy :func:`policy.TLS_FORWARD` allows you to forward queries using `Transport Layer Security`_ protocol, which hides the content of your queries from an attacker observing the network traffic. Further details about this protocol can be found in :rfc:`7858` and `IETF draft dprive-dtls-and-tls-profiles`_.
+
+Queries affected by :func:`policy.TLS_FORWARD` will always be resolved over TLS connection. Knot Resolver does not implement fallback to non-TLS connection, so if TLS connection cannot be established or authenticated according to the configuration, the resolution will fail.
+
+To test this feature you need to either :ref:`configure Knot Resolver as DNS-over-TLS server <tls-server-config>`, or pick some public DNS-over-TLS server. Please see `DNS Privacy Project`_ homepage for list of public servers.
+
+.. note:: Some public DNS-over-TLS providers may apply rate-limiting which
+ makes their service incompatible with Knot Resolver's TLS forwarding.
+ Notably, `Google Public DNS
+ <https://developers.google.com/speed/public-dns/docs/dns-over-tls>`_ doesn't
+ work as of 2019-07-10.
+
+When multiple servers are specified, the one with the lowest round-trip time is used.
+
+CA+hostname authentication
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+Traditional PKI authentication requires server to present certificate with specified hostname, which is issued by one of trusted CAs. Example policy is:
+
+.. code-block:: lua
+
+ 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`_.
+ If you omit that, the system CA certificate store will be used instead (usually sufficient).
+ A list of paths is also accepted, but all of them must be valid PEMs.
+
+Key-pinned authentication
+^^^^^^^^^^^^^^^^^^^^^^^^^
+Instead of CAs, you can specify hashes of accepted certificates in ``pin_sha256``.
+They are in the usual format -- base64 from sha256.
+You may still specify ``hostname`` if you want SNI_ to be sent.
+
+.. _tls-examples:
+
+TLS Examples
+^^^^^^^^^^^^
+
+.. 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'}
+ })
+
+Forwarding to multiple targets
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+With the use of :func:`policy.slice` function, it is possible to split the
+entire DNS namespace into distinct slices. When used in conjunction with
+:func:`policy.TLS_FORWARD`, it's possible to forward different queries to
+different targets.
+
+.. function:: slice(slice_func, action[, action[, ...])
+
+ :param slice_func: slicing function that returns index based on query
+ :param action: action to be performed for the slice
+
+ This function splits the entire domain space into multiple slices (determined
+ by the number of provided ``actions``). A ``slice_func`` is called to determine
+ which slice a query belongs to. The corresponding ``action`` is then executed.
+
+
+.. function:: slice_randomize_psl(seed = os.time() / (3600 * 24 * 7))
+
+ :param seed: seed for random assignment
+
+ The function initializes and returns a slicing function, which
+ deterministically assigns ``query`` to a slice based on the query name.
+
+ It utilizes the `Public Suffix List`_ to ensure domains under the same
+ registrable domain end up in a single slice. (see example below)
+
+ ``seed`` can be used to re-shuffle the slicing algorithm when the slicing
+ function is initialized. By default, the assignment is re-shuffled after one
+ week (when resolver restart / reloads config). To force a stable
+ distribution, pass a fixed value. To re-shuffle on every resolver restart,
+ use ``os.time()``.
+
+ The following example demonstrates a distribution among 3 slices::
+
+ slice 1/3:
+ example.com
+ a.example.com
+ b.example.com
+ x.b.example.com
+ example3.com
+
+ slice 2/3:
+ example2.co.uk
+
+ slice 3/3:
+ example.co.uk
+ a.example.co.uk
+
+These two functions can be used together to forward queries for names
+in different parts of DNS name space to different target servers:
+
+.. code-block:: lua
+
+ policy.add(policy.slice(
+ policy.slice_randomize_psl(),
+ policy.TLS_FORWARD({{'192.0.2.1', hostname='res.example.com'}}),
+ policy.TLS_FORWARD({
+ -- multiple servers can be specified for a single slice
+ -- the one with lowest round-trip time will be used
+ {'193.17.47.1', hostname='odvr.nic.cz'},
+ {'185.43.135.1', hostname='odvr.nic.cz'},
+ })
+ ))
+
+.. note:: The privacy implications of using this feature aren't clear. Since
+ websites often make requests to multiple domains, these might be forwarded
+ to different targets. This could result in decreased privacy (e.g. when the
+ remote targets are both logging or otherwise processing your DNS traffic).
+ The intended use-case is to use this feature with semi-trusted resolvers
+ which claim to do no logging (such as those listed on `dnsprivacy.org
+ <https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Test+Servers>`_), to
+ decrease the potential exposure of your DNS data to a malicious resolver
+ operator.
+
+.. _dns-graft:
+
+Replacing part of the DNS tree
+------------------------------
+
+Following procedure applies only to domains which have different content
+publicly and internally. For example this applies to "your own" top-level domain
+``example.`` which does not exist in the public (global) DNS namespace.
+
+Dealing with these internal-only domains requires extra configuration because
+DNS was designed as "single namespace" and local modifications like adding
+your own TLD break this assumption.
+
+.. warning:: Use of internal names which are not delegated from the public DNS
+ *is causing technical problems* with caching and DNSSEC validation
+ and generally makes DNS operation more costly.
+ We recommend **against** using these non-delegated names.
+
+To make such internal domain available in your resolver it is necessary to
+*graft* your domain onto the public DNS namespace,
+but *grafting* creates new issues:
+
+These *grafted* domains will be rejected by DNSSEC validation
+because such domains are technically indistinguishable from an spoofing attack
+against the public DNS.
+Therefore, if you trust the remote resolver which hosts the internal-only domain,
+and you trust your link to it, you need to use the :func:`policy.STUB` policy
+instead of :func:`policy.FORWARD` to disable DNSSEC validation for those
+*grafted* domains.
+
+.. code-block:: lua
+ :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 policy.STUB is not a chain action.
+ -- Flags: for "dumb" targets disabling EDNS can help (below) as DNSSEC isn't
+ -- validated anyway; in some of those cases adding 'NO_0X20' can also help,
+ -- though it also lowers defenses against off-path attacks on communication
+ -- between the two servers.
+ -- With kresd <= 5.5.3 you also needed 'NO_CACHE' flag to avoid unintentional
+ -- NXDOMAINs that could sometimes happen due to aggressive DNSSEC caching.
+ policy.add(policy.suffix(policy.FLAGS({'NO_EDNS'}), extraTrees))
+ policy.add(policy.suffix(policy.STUB({'2001:db8::1'}), extraTrees))
+
+Response policy zones
+---------------------
+ .. warning::
+
+ There is no published Internet Standard for RPZ_ and implementations vary.
+ At the moment Knot Resolver supports limited subset of RPZ format and deviates
+ from implementation in BIND. Nevertheless it is good enough
+ for blocking large lists of spam or advertising domains.
+
+
+
+ The RPZ file format is basically a DNS zone file with *very special* semantics.
+ For example:
+
+ .. code-block:: none
+
+ ; left hand side ; TTL and class ; right hand side
+ ; encodes RPZ trigger ; ignored ; encodes action
+ ; (i.e. filter)
+ blocked.domain.example 600 IN CNAME . ; block main domain
+ *.blocked.domain.example 600 IN CNAME . ; block subdomains
+
+ The only "trigger" supported in Knot Resolver is query name,
+ i.e. left hand side must be a domain name which triggers the action specified
+ on the right hand side.
+
+ Subset of possible RPZ actions is supported, namely:
+
+ .. csv-table::
+ :header: "RPZ Right Hand Side", "Knot Resolver Action", "BIND Compatibility"
+
+ "``.``", "``action`` is used", "compatible if ``action`` is :any:`policy.DENY`"
+ "``*.``", ":func:`policy.ANSWER`", "yes"
+ "``rpz-passthru.``", ":any:`policy.PASS`", "yes"
+ "``rpz-tcp-only.``", ":any:`policy.TC`", "yes"
+ "``rpz-drop.``", ":any:`policy.DROP`", "no [#]_"
+ "fake A/AAAA", ":func:`policy.ANSWER`", "yes"
+ "fake CNAME", "not supported", "no"
+
+ .. [#] Our :any:`policy.DROP` returns *SERVFAIL* answer (for historical reasons).
+
+
+ .. note::
+
+ To debug which domains are affected by RPZ (or other policy actions), you can enable the ``policy`` log group:
+
+ .. code-block:: lua
+
+ log_groups({'policy'})
+
+ See also :ref:`non-ASCII support note <IDN>`.
+
+
+.. function:: rpz(action, path, [watch = true])
+
+ :param action: the default action for match in the zone; typically you want :any:`policy.DENY`
+ :param path: path to zone file
+ :param watch: boolean, if true, the file will be reloaded on file change
+
+ Enforce RPZ_ rules. This can be used in conjunction with published blocklist feeds.
+ The RPZ_ operation is well described in this `Jan-Piet Mens's post`_,
+ or the `Pro DNS and BIND`_ book.
+
+ For example, we can store the example snippet with domain ``blocked.domain.example``
+ (above) into file ``/etc/knot-resolver/blocklist.rpz`` and configure resolver to
+ answer with *NXDOMAIN* plus the specified additional text to queries for this domain:
+
+ .. code-block:: lua
+
+ policy.add(
+ policy.rpz(policy.DENY_MSG('domain blocked by your resolver operator'),
+ '/etc/knot-resolver/blocklist.rpz',
+ true))
+
+ Resolver will reload RPZ file at run-time if the RPZ file changes.
+ Recommended RPZ update procedure is to store new blocklist in a new file
+ (*newblocklist.rpz*) and then rename the new file to the original file name
+ (*blocklist.rpz*). This avoids problems where resolver might attempt
+ to re-read an incomplete file.
+
+
+
+Additional properties
+---------------------
+
+Most properties (actions, filters) are described above.
+
+.. function:: add(rule, postrule)
+
+ :param rule: added rule, i.e. ``policy.pattern(policy.DENY, '[0-9]+\2cz')``
+ :param postrule: boolean, if true the rule will be evaluated on answer instead of query
+ :return: rule description
+
+ Add a new policy rule that is executed either or queries or answers, depending on the ``postrule`` parameter. You can then use the returned rule description to get information and unique identifier for the rule, as well as match count.
+
+ .. code-block:: lua
+
+ -- mirror all queries, 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)
+
+ :param id: identifier of a given rule returned by :func:`policy.add`
+ :return: boolean ``true`` if rule was deleted, ``false`` otherwise
+
+ Remove a rule from policy list.
+
+.. function:: todnames({name, ...})
+
+ :param: names table of domain names in textual format
+
+ Returns table of domain names in wire format converted from strings.
+
+ .. code-block:: lua
+
+ -- Convert single name
+ assert(todname('example.com') == '\7example\3com\0')
+ -- Convert table of names
+ policy.todnames({'example.com', 'me.cz'})
+ { '\7example\3com\0', '\2me\2cz\0' }
+
+
+.. _RPZ: https://dnsrpz.info/
+.. _`PEM format`: https://en.wikipedia.org/wiki/Privacy-enhanced_Electronic_Mail
+.. _`Pro DNS and BIND`: http://www.zytrax.com/books/dns/ch7/rpz.html
+.. _`Jan-Piet Mens's post`: http://jpmens.net/2011/04/26/how-to-configure-your-bind-resolvers-to-lie-using-response-policy-zones-rpz/
+.. _`Transport Layer Security`: https://en.wikipedia.org/wiki/Transport_Layer_Security
+.. _`DNS Privacy Project`: https://dnsprivacy.org/
+.. _`IETF draft dprive-dtls-and-tls-profiles`: https://tools.ietf.org/html/draft-ietf-dprive-dtls-and-tls-profiles
+.. _SNI: https://en.wikipedia.org/wiki/Server_Name_Indication
+.. _`Public Suffix List`: https://publicsuffix.org
diff --git a/modules/policy/lua-aho-corasick/LICENSE b/modules/policy/lua-aho-corasick/LICENSE
new file mode 100644
index 0000000..dd65f72
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/LICENSE
@@ -0,0 +1,28 @@
+ Copyright (c) 2014 CloudFlare, Inc. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following disclaimer
+ in the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of CloudFlare, Inc. nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/modules/policy/lua-aho-corasick/Makefile b/modules/policy/lua-aho-corasick/Makefile
new file mode 100644
index 0000000..6471664
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/Makefile
@@ -0,0 +1,134 @@
+OS := $(shell uname)
+
+ifeq ($(OS), Darwin)
+ SO_EXT := dylib
+else
+ SO_EXT := so
+endif
+
+#############################################################################
+#
+# Binaries we are going to build
+#
+#############################################################################
+#
+C_SO_NAME = libac.$(SO_EXT)
+LUA_SO_NAME = ahocorasick.$(SO_EXT)
+AR_NAME = libac.a
+
+#############################################################################
+#
+# Compile and link flags
+#
+#############################################################################
+PREFIX ?= /usr/local
+LUA_VERSION := 5.1
+LUA_INCLUDE_DIR := $(PREFIX)/include/lua$(LUA_VERSION)
+SO_TARGET_DIR := $(PREFIX)/lib/lua/$(LUA_VERSION)
+LUA_TARGET_DIR := $(PREFIX)/share/lua/$(LUA_VERSION)
+
+# Available directives:
+# -DDEBUG : Turn on debugging support
+# -DVERIFY : To verify if the slow-version and fast-version implementations
+# get exactly the same result. Note -DVERIFY implies -DDEBUG.
+#
+COMMON_FLAGS = -O3 #-g -DVERIFY -msse2 -msse3 -msse4.1
+COMMON_FLAGS += -fvisibility=hidden -Wall $(CXXFLAGS) $(MY_CXXFLAGS) $(CPPFLAGS)
+
+SO_CXXFLAGS = $(COMMON_FLAGS) -fPIC
+SO_LFLAGS = $(COMMON_FLAGS) $(LDFLAGS)
+AR_CXXFLAGS = $(COMMON_FLAGS)
+
+# -DVERIFY implies -DDEBUG
+ifneq ($(findstring -DVERIFY, $(COMMON_FLAGS)), )
+ifeq ($(findstring -DDEBUG, $(COMMON_FLAGS)), )
+ COMMON_FLAGS += -DDEBUG
+endif
+endif
+
+AR = ar
+AR_FLAGS = cru
+
+#############################################################################
+#
+# Divide source codes and objects into several categories
+#
+#############################################################################
+#
+SRC_COMMON := ac_fast.cxx ac_slow.cxx
+LIBAC_SO_SRC := $(SRC_COMMON) ac.cxx # source for libac.so
+LUA_SO_SRC := $(SRC_COMMON) ac_lua.cxx # source for ahocorasick.so
+LIBAC_A_SRC := $(LIBAC_SO_SRC) # source for libac.a
+
+#############################################################################
+#
+# Make rules
+#
+#############################################################################
+#
+.PHONY = all clean test benchmark prepare
+all : $(C_SO_NAME) $(LUA_SO_NAME) $(AR_NAME)
+
+-include c_so_dep.txt
+-include lua_so_dep.txt
+-include ar_dep.txt
+
+BUILD_SO_DIR := build_so
+BUILD_AR_DIR := build_ar
+
+$(BUILD_SO_DIR) :; mkdir $@
+$(BUILD_AR_DIR) :; mkdir $@
+
+$(BUILD_SO_DIR)/%.o : %.cxx | $(BUILD_SO_DIR)
+ $(CXX) $< -c $(SO_CXXFLAGS) -I$(LUA_INCLUDE_DIR) -MMD -o $@
+
+$(BUILD_AR_DIR)/%.o : %.cxx | $(BUILD_AR_DIR)
+ $(CXX) $< -c $(AR_CXXFLAGS) -I$(LUA_INCLUDE_DIR) -MMD -o $@
+
+ifneq ($(OS), Darwin)
+$(C_SO_NAME) : $(addprefix $(BUILD_SO_DIR)/, ${LIBAC_SO_SRC:.cxx=.o})
+ $(CXX) $+ -shared -Wl,-soname=$(C_SO_NAME) $(SO_LFLAGS) -o $@
+ cat $(addprefix $(BUILD_SO_DIR)/, ${LIBAC_SO_SRC:.cxx=.d}) > c_so_dep.txt
+
+$(LUA_SO_NAME) : $(addprefix $(BUILD_SO_DIR)/, ${LUA_SO_SRC:.cxx=.o})
+ $(CXX) $+ -shared -Wl,-soname=$(LUA_SO_NAME) $(SO_LFLAGS) -o $@
+ cat $(addprefix $(BUILD_SO_DIR)/, ${LUA_SO_SRC:.cxx=.d}) > lua_so_dep.txt
+
+else
+$(C_SO_NAME) : $(addprefix $(BUILD_SO_DIR)/, ${LIBAC_SO_SRC:.cxx=.o})
+ $(CXX) $+ -shared $(SO_LFLAGS) -o $@
+ cat $(addprefix $(BUILD_SO_DIR)/, ${LIBAC_SO_SRC:.cxx=.d}) > c_so_dep.txt
+
+$(LUA_SO_NAME) : $(addprefix $(BUILD_SO_DIR)/, ${LUA_SO_SRC:.cxx=.o})
+ $(CXX) $+ -shared $(SO_LFLAGS) -o $@ -Wl,-undefined,dynamic_lookup
+ cat $(addprefix $(BUILD_SO_DIR)/, ${LUA_SO_SRC:.cxx=.d}) > lua_so_dep.txt
+endif
+
+$(AR_NAME) : $(addprefix $(BUILD_AR_DIR)/, ${LIBAC_A_SRC:.cxx=.o})
+ $(AR) $(AR_FLAGS) $@ $+
+ cat $(addprefix $(BUILD_AR_DIR)/, ${LIBAC_A_SRC:.cxx=.d}) > lua_so_dep.txt
+
+#############################################################################
+#
+# Misc
+#
+#############################################################################
+#
+test : $(C_SO_NAME)
+ $(MAKE) -C tests && \
+ luajit tests/lua_test.lua && \
+ luajit tests/load_ac_test.lua
+
+benchmark: $(C_SO_NAME)
+ $(MAKE) benchmark -C tests
+
+clean :
+ -rm -rf *.o *.d c_so_dep.txt lua_so_dep.txt ar_dep.txt $(TEST) \
+ $(C_SO_NAME) $(LUA_SO_NAME) $(TEST) $(BUILD_SO_DIR) $(BUILD_AR_DIR) \
+ $(AR_NAME)
+ make clean -C tests
+
+install:
+ install -D -m 755 $(C_SO_NAME) $(DESTDIR)/$(SO_TARGET_DIR)/$(C_SO_NAME)
+ install -D -m 755 $(LUA_SO_NAME) $(DESTDIR)/$(SO_TARGET_DIR)/$(LUA_SO_NAME)
+ install -D -m 664 load_ac.lua $(DESTDIR)/$(LUA_TARGET_DIR)/load_ac.lua
diff --git a/modules/policy/lua-aho-corasick/README.md b/modules/policy/lua-aho-corasick/README.md
new file mode 100644
index 0000000..b5cc406
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/README.md
@@ -0,0 +1,40 @@
+aho-corasick-lua
+================
+
+C++ and Lua Implementation of the Aho-Corasick (AC) string matching algorithm
+(http://dl.acm.org/citation.cfm?id=360855).
+
+We began with pure Lua implementation and realize the performance is not
+satisfactory. So we switch to C/C++ implementation.
+
+There are two shared objects provied by this package: libac.so and ahocorasick.so
+The former is a regular shared object which can be directly used by C/C++
+application, or by Lua via FFI; and the later is a Lua module. An example usage
+is shown bellow:
+
+```lua
+local ac = require "ahocorasick"
+local dict = {"string1", "string", "etc"}
+local acinst = ac.create(dict)
+local r = ac.match(acinst, "mystring")
+```
+
+For efficiency reasons, the implementation is slightly different from the
+standard AC algorithm in that it doesn't return a set of strings in the dictionary
+that match the given string, instead it only returns one of them in case the string
+matches. The functionality of our implementation can be (precisely) described by
+following pseudo-c snippet.
+
+```C
+string foo(input-string, dictionary) {
+ string ret = the-end-of-input-string;
+ for each string s in dictionary {
+ // find the first occurrence match sub-string.
+ ret = min(ret, strstr(input-string, s);
+ }
+ return ret;
+}
+```
+
+It's pretty easy to get rid of this limitation, just to associate each state with
+a spare bit-vector dipicting the set of strings recognized by that state.
diff --git a/modules/policy/lua-aho-corasick/ac.cxx b/modules/policy/lua-aho-corasick/ac.cxx
new file mode 100644
index 0000000..23fb3b5
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/ac.cxx
@@ -0,0 +1,101 @@
+// Interface functions for libac.so
+//
+#include "ac_slow.hpp"
+#include "ac_fast.hpp"
+#include "ac.h"
+
+static inline ac_result_t
+_match(buf_header_t* ac, const char* str, unsigned int len) {
+ AC_Buffer* buf = (AC_Buffer*)(void*)ac;
+ ASSERT(ac->magic_num == AC_MAGIC_NUM);
+
+ ac_result_t r = Match(buf, str, len);
+
+ #ifdef VERIFY
+ {
+ Match_Result r2 = buf->slow_impl->Match(str, len);
+ if (r.match_begin != r2.begin) {
+ ASSERT(0);
+ } else {
+ ASSERT((r.match_begin < 0) ||
+ (r.match_end == r2.end &&
+ r.pattern_idx == r2.pattern_idx));
+ }
+ }
+ #endif
+ return r;
+}
+
+extern "C" int
+ac_match2(ac_t* ac, const char* str, unsigned int len) {
+ ac_result_t r = _match((buf_header_t*)(void*)ac, str, len);
+ return r.match_begin;
+}
+
+extern "C" ac_result_t
+ac_match(ac_t* ac, const char* str, unsigned int len) {
+ return _match((buf_header_t*)(void*)ac, str, len);
+}
+
+extern "C" ac_result_t
+ac_match_longest_l(ac_t* ac, const char* str, unsigned int len) {
+ AC_Buffer* buf = (AC_Buffer*)(void*)ac;
+ ASSERT(((buf_header_t*)ac)->magic_num == AC_MAGIC_NUM);
+
+ ac_result_t r = Match_Longest_L(buf, str, len);
+ return r;
+}
+
+class BufAlloc : public Buf_Allocator {
+public:
+ virtual AC_Buffer* alloc(int sz) {
+ return (AC_Buffer*)(new unsigned char[sz]);
+ }
+
+ // Do not de-allocate the buffer when the BufAlloc die.
+ virtual void free() {}
+
+ static void myfree(AC_Buffer* buf) {
+ ASSERT(buf->hdr.magic_num == AC_MAGIC_NUM);
+ const char* b = (const char*)buf;
+ delete[] b;
+ }
+};
+
+extern "C" ac_t*
+ac_create(const char** strv, unsigned int* strlenv, unsigned int v_len) {
+ if (v_len >= 65535) {
+ // TODO: Currently we use 16-bit to encode pattern-index (see the
+ // comment to AC_State::is_term), therefore we are not able to
+ // handle pattern set with more than 65535 entries.
+ return 0;
+ }
+
+ ACS_Constructor *acc;
+#ifdef VERIFY
+ acc = new ACS_Constructor;
+#else
+ ACS_Constructor tmp;
+ acc = &tmp;
+#endif
+ acc->Construct(strv, strlenv, v_len);
+
+ BufAlloc ba;
+ AC_Converter cvt(*acc, ba);
+ AC_Buffer* buf = cvt.Convert();
+
+#ifdef VERIFY
+ buf->slow_impl = acc;
+#endif
+ return (ac_t*)(void*)buf;
+}
+
+extern "C" void
+ac_free(void* ac) {
+ AC_Buffer* buf = (AC_Buffer*)ac;
+#ifdef VERIFY
+ delete buf->slow_impl;
+#endif
+
+ BufAlloc::myfree(buf);
+}
diff --git a/modules/policy/lua-aho-corasick/ac.h b/modules/policy/lua-aho-corasick/ac.h
new file mode 100644
index 0000000..30bf447
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/ac.h
@@ -0,0 +1,49 @@
+#ifndef AC_H
+#define AC_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define AC_EXPORT __attribute__ ((visibility ("default")))
+
+/* If the subject-string dosen't match any of the given patterns, "match_begin"
+ * should be a negative; otherwise the substring of the subject-string,
+ * starting from offset "match_begin" to "match_end" incusively,
+ * should exactly match the pattern specified by the 'pattern_idx' (i.e.
+ * the pattern is "pattern_v[pattern_idx]" where the "pattern_v" is the
+ * first acutal argument passing to ac_create())
+ */
+typedef struct {
+ int match_begin;
+ int match_end;
+ int pattern_idx;
+} ac_result_t;
+
+struct ac_t;
+
+/* Create an AC instance. "pattern_v" is a vector of patterns, the length of
+ * i-th pattern is specified by "pattern_len_v[i]"; the number of patterns
+ * is specified by "vect_len".
+ *
+ * Return the instance on success, or NUL otherwise.
+ */
+ac_t* ac_create(const char** pattern_v, unsigned int* pattern_len_v,
+ unsigned int vect_len) AC_EXPORT;
+
+ac_result_t ac_match(ac_t*, const char *str, unsigned int len) AC_EXPORT;
+
+ac_result_t ac_match_longest_l(ac_t*, const char *str, unsigned int len) AC_EXPORT;
+
+/* Similar to ac_match() except that it only returns match-begin. The rationale
+ * for this interface is that luajit has hard time in dealing with strcture-
+ * return-value.
+ */
+int ac_match2(ac_t*, const char *str, unsigned int len) AC_EXPORT;
+
+void ac_free(void*) AC_EXPORT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AC_H */
diff --git a/modules/policy/lua-aho-corasick/ac_fast.cxx b/modules/policy/lua-aho-corasick/ac_fast.cxx
new file mode 100644
index 0000000..9dbc2e6
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/ac_fast.cxx
@@ -0,0 +1,468 @@
+#include <algorithm> // for std::sort
+#include "ac_slow.hpp"
+#include "ac_fast.hpp"
+
+uint32
+AC_Converter::Calc_State_Sz(const ACS_State* s) const {
+ AC_State dummy;
+ uint32 sz = offsetof(AC_State, input_vect);
+ sz += s->Get_GotoNum() * sizeof(dummy.input_vect[0]);
+
+ if (sz < sizeof(AC_State))
+ sz = sizeof(AC_State);
+
+ uint32 align = __alignof__(dummy);
+ sz = (sz + align - 1) & ~(align - 1);
+ return sz;
+}
+
+AC_Buffer*
+AC_Converter::Alloc_Buffer() {
+ const vector<ACS_State*>& all_states = _acs.Get_All_States();
+ const ACS_State* root_state = _acs.Get_Root_State();
+ uint32 root_fanout = root_state->Get_GotoNum();
+
+ // Step 1: Calculate the buffer size
+ AC_Ofst root_goto_ofst, states_ofst_ofst, first_state_ofst;
+
+ // part 1 : buffer header
+ uint32 sz = root_goto_ofst = sizeof(AC_Buffer);
+
+ // part 2: Root-node's goto function
+ if (likely(root_fanout != 255))
+ sz += 256;
+ else
+ root_goto_ofst = 0;
+
+ // part 3: mapping of state's relative position.
+ unsigned align = __alignof__(AC_Ofst);
+ sz = (sz + align - 1) & ~(align - 1);
+ states_ofst_ofst = sz;
+
+ sz += sizeof(AC_Ofst) * all_states.size();
+
+ // part 4: state's contents
+ align = __alignof__(AC_State);
+ sz = (sz + align - 1) & ~(align - 1);
+ first_state_ofst = sz;
+
+ uint32 state_sz = 0;
+ for (vector<ACS_State*>::const_iterator i = all_states.begin(),
+ e = all_states.end(); i != e; i++) {
+ state_sz += Calc_State_Sz(*i);
+ }
+ state_sz -= Calc_State_Sz(root_state);
+
+ sz += state_sz;
+
+ // Step 2: Allocate buffer, and populate header.
+ AC_Buffer* buf = _buf_alloc.alloc(sz);
+
+ buf->hdr.magic_num = AC_MAGIC_NUM;
+ buf->hdr.impl_variant = IMPL_FAST_VARIANT;
+ buf->buf_len = sz;
+ buf->root_goto_ofst = root_goto_ofst;
+ buf->states_ofst_ofst = states_ofst_ofst;
+ buf->first_state_ofst = first_state_ofst;
+ buf->root_goto_num = root_fanout;
+ buf->state_num = _acs.Get_State_Num();
+ return buf;
+}
+
+void
+AC_Converter::Populate_Root_Goto_Func(AC_Buffer* buf,
+ GotoVect& goto_vect) {
+ unsigned char *buf_base = (unsigned char*)(buf);
+ InputTy* root_gotos = (InputTy*)(buf_base + buf->root_goto_ofst);
+ const ACS_State* root_state = _acs.Get_Root_State();
+
+ root_state->Get_Sorted_Gotos(goto_vect);
+
+ // Renumber the ID of root-node's immediate kids.
+ uint32 new_id = 1;
+ bool full_fantout = (goto_vect.size() == 255);
+ if (likely(!full_fantout))
+ bzero(root_gotos, 256*sizeof(InputTy));
+
+ for (GotoVect::iterator i = goto_vect.begin(), e = goto_vect.end();
+ i != e; i++, new_id++) {
+ InputTy c = i->first;
+ ACS_State* s = i->second;
+ _id_map[s->Get_ID()] = new_id;
+
+ if (likely(!full_fantout))
+ root_gotos[c] = new_id;
+ }
+}
+
+AC_Buffer*
+AC_Converter::Convert() {
+ // Step 1: Some preparation stuff.
+ GotoVect gotovect;
+
+ _id_map.clear();
+ _ofst_map.clear();
+ _id_map.resize(_acs.Get_Next_Node_Id());
+ _ofst_map.resize(_acs.Get_Next_Node_Id());
+
+ // Step 2: allocate buffer to accommodate the entire AC graph.
+ AC_Buffer* buf = Alloc_Buffer();
+ unsigned char* buf_base = (unsigned char*)buf;
+
+ // Step 3: Root node need special care.
+ Populate_Root_Goto_Func(buf, gotovect);
+ buf->root_goto_num = gotovect.size();
+ _id_map[_acs.Get_Root_State()->Get_ID()] = 0;
+
+ // Step 4: Converting the remaining states by BFSing the graph.
+ // First of all, enter root's immediate kids to the working list.
+ vector<const ACS_State*> wl;
+ State_ID id = 1;
+ for (GotoVect::iterator i = gotovect.begin(), e = gotovect.end();
+ i != e; i++, id++) {
+ ACS_State* s = i->second;
+ wl.push_back(s);
+ _id_map[s->Get_ID()] = id;
+ }
+
+ AC_Ofst* state_ofst_vect = (AC_Ofst*)(buf_base + buf->states_ofst_ofst);
+ AC_Ofst ofst = buf->first_state_ofst;
+ for (uint32 idx = 0; idx < wl.size(); idx++) {
+ const ACS_State* old_s = wl[idx];
+ AC_State* new_s = (AC_State*)(buf_base + ofst);
+
+ // This property should hold as we:
+ // - States are appended to worklist in the BFS order.
+ // - sibiling states are appended to worklist in the order of their
+ // corresponding input.
+ //
+ State_ID state_id = idx + 1;
+ ASSERT(_id_map[old_s->Get_ID()] == state_id);
+
+ state_ofst_vect[state_id] = ofst;
+
+ new_s->first_kid = wl.size() + 1;
+ new_s->depth = old_s->Get_Depth();
+ new_s->is_term = old_s->is_Terminal() ?
+ old_s->get_Pattern_Idx() + 1 : 0;
+
+ uint32 gotonum = old_s->Get_GotoNum();
+ new_s->goto_num = gotonum;
+
+ // Populate the "input" field
+ old_s->Get_Sorted_Gotos(gotovect);
+ uint32 input_idx = 0;
+ uint32 id = wl.size() + 1;
+ InputTy* input_vect = new_s->input_vect;
+ for (GotoVect::iterator i = gotovect.begin(), e = gotovect.end();
+ i != e; i++, id++, input_idx++) {
+ input_vect[input_idx] = i->first;
+
+ ACS_State* kid = i->second;
+ _id_map[kid->Get_ID()] = id;
+ wl.push_back(kid);
+ }
+
+ _ofst_map[old_s->Get_ID()] = ofst;
+ ofst += Calc_State_Sz(old_s);
+ }
+
+ // This assertion might be useful to catch buffer overflow
+ ASSERT(ofst == buf->buf_len);
+
+ // Populate the fail-link field.
+ for (vector<const ACS_State*>::iterator i = wl.begin(), e = wl.end();
+ i != e; i++) {
+ const ACS_State* slow_s = *i;
+ State_ID fast_s_id = _id_map[slow_s->Get_ID()];
+ AC_State* fast_s = (AC_State*)(buf_base + state_ofst_vect[fast_s_id]);
+ if (const ACS_State* fl = slow_s->Get_FailLink()) {
+ State_ID id = _id_map[fl->Get_ID()];
+ fast_s->fail_link = id;
+ } else
+ fast_s->fail_link = 0;
+ }
+#ifdef DEBUG
+ //dump_buffer(buf, stderr);
+#endif
+ return buf;
+}
+
+static inline AC_State*
+Get_State_Addr(unsigned char* buf_base, AC_Ofst* StateOfstVect, uint32 state_id) {
+ ASSERT(state_id != 0 && "root node is handled in speical way");
+ ASSERT(state_id < ((AC_Buffer*)buf_base)->state_num);
+ return (AC_State*)(buf_base + StateOfstVect[state_id]);
+}
+
+// The performance of the binary search is critical to this work.
+//
+// Here we provide two versions of binary-search functions.
+// The non-pristine version seems to consistently out-perform "pristine" one on
+// bunch of benchmarks we tested. With the benchmark under tests/testinput/
+//
+// The speedup is following on my laptop (core i7, ubuntu):
+//
+// benchmark was is
+// ----------------------------------------
+// image.bin 2.3s 2.0s
+// test.tar 6.7s 5.7s
+//
+// NOTE: As of I write this comment, we only measure the performance on about
+// 10+ benchmarks. It's still too early to say which one works better.
+//
+#if !defined(BS_MULTI_VER)
+static bool __attribute__((always_inline)) inline
+Binary_Search_Input(InputTy* input_vect, int vect_len, InputTy input, int& idx) {
+ if (vect_len <= 8) {
+ for (int i = 0; i < vect_len; i++) {
+ if (input_vect[i] == input) {
+ idx = i;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // The "low" and "high" must be signed integers, as they could become -1.
+ // Also since they are signed integer, "(low + high)/2" is sightly more
+ // expensive than (low+high)>>1 or ((unsigned)(low + high))/2.
+ //
+ int low = 0, high = vect_len - 1;
+ while (low <= high) {
+ int mid = (low + high) >> 1;
+ InputTy mid_c = input_vect[mid];
+
+ if (input < mid_c)
+ high = mid - 1;
+ else if (input > mid_c)
+ low = mid + 1;
+ else {
+ idx = mid;
+ return true;
+ }
+ }
+ return false;
+}
+
+#else
+
+/* Let us call this version "pristine" version. */
+static inline bool
+Binary_Search_Input(InputTy* input_vect, int vect_len, InputTy input, int& idx) {
+ int low = 0, high = vect_len - 1;
+ while (low <= high) {
+ int mid = (low + high) >> 1;
+ InputTy mid_c = input_vect[mid];
+
+ if (input < mid_c)
+ high = mid - 1;
+ else if (input > mid_c)
+ low = mid + 1;
+ else {
+ idx = mid;
+ return true;
+ }
+ }
+ return false;
+}
+#endif
+
+typedef enum {
+ // Look for the first match. e.g. pattern set = {"ab", "abc", "def"},
+ // subject string "ababcdef". The first match would be "ab" at the
+ // beginning of the subject string.
+ MV_FIRST_MATCH,
+
+ // Look for the left-most longest match. Follow above example; there are
+ // two longest matches, "abc" and "def", and the left-most longest match
+ // is "abc".
+ MV_LEFT_LONGEST,
+
+ // Similar to the left-most longest match, except that it returns the
+ // *right* most longest match. Follow above example, the match would
+ // be "def". NYI.
+ MV_RIGHT_LONGEST,
+
+ // Return all patterns that match that given subject string. NYI.
+ MV_ALL_MATCHES,
+} MATCH_VARIANT;
+
+/* The Match_Tmpl is the template for vairants MV_FIRST_MATCH, MV_LEFT_LONGEST,
+ * MV_RIGHT_LONGEST (If we really really need MV_RIGHT_LONGEST variant, we are
+ * better off implementing it in a seprate function).
+ *
+ * The Match_Tmpl supports three variants at once "symbolically", once it's
+ * instanced to a particular variants, all the code irrelevant to the variants
+ * will be statically removed. So don't worry about the code like
+ * "if (variant == MV_XXXX)"; they will not incur any penalty.
+ *
+ * The drawback of using template is increased code size. Unfortunately, there
+ * is no silver bullet.
+ */
+template<MATCH_VARIANT variant> static ac_result_t
+Match_Tmpl(AC_Buffer* buf, const char* str, uint32 len) {
+ unsigned char* buf_base = (unsigned char*)(buf);
+ unsigned char* root_goto = buf_base + buf->root_goto_ofst;
+ AC_Ofst* states_ofst_vect = (AC_Ofst* )(buf_base + buf->states_ofst_ofst);
+
+ AC_State* state = 0;
+ uint32 idx = 0;
+
+ // Skip leading chars that are not valid input of root-nodes.
+ if (likely(buf->root_goto_num != 255)) {
+ while(idx < len) {
+ unsigned char c = str[idx++];
+ if (unsigned char kid_id = root_goto[c]) {
+ state = Get_State_Addr(buf_base, states_ofst_vect, kid_id);
+ break;
+ }
+ }
+ } else {
+ idx = 1;
+ state = Get_State_Addr(buf_base, states_ofst_vect, *str);
+ }
+
+ ac_result_t r = {-1, -1};
+ if (likely(state != 0)) {
+ if (unlikely(state->is_term)) {
+ /* Dictionary may have string of length 1 */
+ r.match_begin = idx - state->depth;
+ r.match_end = idx - 1;
+ r.pattern_idx = state->is_term - 1;
+
+ if (variant == MV_FIRST_MATCH) {
+ return r;
+ }
+ }
+ }
+
+ while (idx < len) {
+ unsigned char c = str[idx];
+ int res;
+ bool found;
+ found = Binary_Search_Input(state->input_vect, state->goto_num, c, res);
+ if (found) {
+ // The "t = goto(c, current_state)" is valid, advance to state "t".
+ uint32 kid = state->first_kid + res;
+ state = Get_State_Addr(buf_base, states_ofst_vect, kid);
+ idx++;
+ } else {
+ // Follow the fail-link.
+ State_ID fl = state->fail_link;
+ if (fl == 0) {
+ // fail-link is root-node, which implies the root-node dosen't
+ // have 255 valid transitions (otherwise, the fail-link should
+ // points to "goto(root, c)"), so we don't need speical handling
+ // as we did before this while-loop is entered.
+ //
+ while(idx < len) {
+ InputTy c = str[idx++];
+ if (unsigned char kid_id = root_goto[c]) {
+ state =
+ Get_State_Addr(buf_base, states_ofst_vect, kid_id);
+ break;
+ }
+ }
+ } else {
+ state = Get_State_Addr(buf_base, states_ofst_vect, fl);
+ }
+ }
+
+ // Check to see if the state is terminal state?
+ if (state->is_term) {
+ if (variant == MV_FIRST_MATCH) {
+ ac_result_t r;
+ r.match_begin = idx - state->depth;
+ r.match_end = idx - 1;
+ r.pattern_idx = state->is_term - 1;
+ return r;
+ }
+
+ if (variant == MV_LEFT_LONGEST) {
+ int match_begin = idx - state->depth;
+ int match_end = idx - 1;
+
+ if (r.match_begin == -1 ||
+ match_end - match_begin > r.match_end - r.match_begin) {
+ r.match_begin = match_begin;
+ r.match_end = match_end;
+ r.pattern_idx = state->is_term - 1;
+ }
+ continue;
+ }
+
+ ASSERT(false && "NYI");
+ }
+ }
+
+ return r;
+}
+
+ac_result_t
+Match(AC_Buffer* buf, const char* str, uint32 len) {
+ return Match_Tmpl<MV_FIRST_MATCH>(buf, str, len);
+}
+
+ac_result_t
+Match_Longest_L(AC_Buffer* buf, const char* str, uint32 len) {
+ return Match_Tmpl<MV_LEFT_LONGEST>(buf, str, len);
+}
+
+#ifdef DEBUG
+void
+AC_Converter::dump_buffer(AC_Buffer* buf, FILE* f) {
+ vector<AC_Ofst> state_ofst;
+ state_ofst.resize(_id_map.size());
+
+ fprintf(f, "Id maps between old/slow and new/fast graphs\n");
+ int old_id = 0;
+ for (vector<uint32>::iterator i = _id_map.begin(), e = _id_map.end();
+ i != e; i++, old_id++) {
+ State_ID new_id = *i;
+ if (new_id != 0) {
+ fprintf(f, "%d -> %d, ", old_id, new_id);
+ }
+ }
+ fprintf(f, "\n");
+
+ int idx = 0;
+ for (vector<uint32>::iterator i = _id_map.begin(), e = _id_map.end();
+ i != e; i++, idx++) {
+ uint32 id = *i;
+ if (id == 0) continue;
+ state_ofst[id] = _ofst_map[idx];
+ }
+
+ unsigned char* buf_base = (unsigned char*)buf;
+
+ // dump root goto-function.
+ fprintf(f, "root, fanout:%d goto {", buf->root_goto_num);
+ if (buf->root_goto_num != 255) {
+ unsigned char* root_goto = buf_base + buf->root_goto_ofst;
+ for (uint32 i = 0; i < 255; i++) {
+ if (root_goto[i] != 0)
+ fprintf(f, "%c->S:%d, ", (unsigned char)i, root_goto[i]);
+ }
+ } else {
+ fprintf(f, "full fanout\n");
+ }
+ fprintf(f, "}\n");
+
+ // dump remaining states.
+ AC_Ofst* state_ofst_vect = (AC_Ofst*)(buf_base + buf->states_ofst_ofst);
+ for (uint32 i = 1, e = buf->state_num; i < e; i++) {
+ AC_Ofst ofst = state_ofst_vect[i];
+ ASSERT(ofst == state_ofst[i]);
+ fprintf(f, "S:%d, ofst:%d, goto={", i, ofst);
+
+ AC_State* s = (AC_State*)(buf_base + ofst);
+ State_ID kid = s->first_kid;
+ for (uint32 k = 0, ke = s->goto_num; k < ke; k++, kid++)
+ fprintf(f, "%c->S:%d, ", s->input_vect[k], kid);
+
+ fprintf(f, "}, fail-link = S:%d, %s\n", s->fail_link,
+ s->is_term ? "terminal" : "");
+ }
+}
+#endif
diff --git a/modules/policy/lua-aho-corasick/ac_fast.hpp b/modules/policy/lua-aho-corasick/ac_fast.hpp
new file mode 100644
index 0000000..9ac557c
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/ac_fast.hpp
@@ -0,0 +1,124 @@
+#ifndef AC_FAST_H
+#define AC_FAST_H
+
+#include <vector>
+#include "ac.h"
+#include "ac_slow.hpp"
+
+using namespace std;
+
+class ACS_Constructor;
+
+typedef uint32 AC_Ofst;
+typedef uint32 State_ID;
+
+// The entire "fast" AC graph is converted from its "slow" version, and store
+// in an consecutive trunk of memory or "buffer". Since the pointers in the
+// fast AC graph are represented as offset relative to the base address of
+// the buffer, this fast AC graph is position-independent, meaning cloning
+// the fast graph is just to memcpy the entire buffer.
+//
+// The buffer is laid-out as following:
+//
+// 1. The buffer header. (i.e. the AC_Buffer content)
+// 2. root-node's goto functions. It is represented as an array indiced by
+// root-node's valid inputs, and the element is the ID of the corresponding
+// transition state (aka kid). To save space, we used 8-bit to represent
+// the IDs. ID of root's kids starts with 1.
+//
+// Root may have 255 valid inputs. In this speical case, i-th element
+// stores value i -- i.e the i-th state. So, we don't need such array
+// at all. On the other hand, 8-bit is insufficient to encode kids' ID.
+//
+// 3. An array indiced by state's id, and the element is the offset
+// of correspoding state wrt the base address of the buffer.
+//
+// 4. the contents of states.
+//
+typedef struct {
+ buf_header_t hdr; // The header exposed to the user using this lib.
+#ifdef VERIFY
+ ACS_Constructor* slow_impl;
+#endif
+ uint32 buf_len;
+ AC_Ofst root_goto_ofst; // addr of root node's goto() function.
+ AC_Ofst states_ofst_ofst; // addr of state pointer vector (indiced by id)
+ AC_Ofst first_state_ofst; // addr of the first state in the buffer.
+ uint16 root_goto_num; // fan-out of root-node.
+ uint16 state_num; // number of states
+
+ // Followed by the gut of the buffer:
+ // 1. map: root's-valid-input -> kid's id
+ // 2. map: state's ID -> offset of the state
+ // 3. states' content.
+} AC_Buffer;
+
+// Depict the state of "fast" AC graph.
+typedef struct {
+ // transition are sorted. For instance, state s1, has two transitions :
+ // goto(b) -> S_b, goto(a)->S_a. The inputs are sorted in the ascending
+ // order, and the target states are permuted accordingly. In this case,
+ // the inputs are sorted as : a, b, and the target states are permuted
+ // into S_a, S_b. So, S_a is the 1st kid, the ID of kids are consecutive,
+ // so we don't need to save all the target kids.
+ //
+ State_ID first_kid;
+ AC_Ofst fail_link;
+ short depth; // How far away from root.
+ unsigned short is_term; // Is terminal node. if is_term != 0, it encodes
+ // the value of "1 + pattern-index".
+ unsigned char goto_num; // The number of valid transition.
+ InputTy input_vect[1]; // Vector of valid input. Must be last field!
+} AC_State;
+
+class Buf_Allocator {
+public:
+ Buf_Allocator() : _buf(0) {}
+ virtual ~Buf_Allocator() { free(); }
+
+ virtual AC_Buffer* alloc(int sz) = 0;
+ virtual void free() {};
+protected:
+ AC_Buffer* _buf;
+};
+
+// Convert slow-AC-graph into fast one.
+class AC_Converter {
+public:
+ AC_Converter(ACS_Constructor& acs, Buf_Allocator& ba) :
+ _acs(acs), _buf_alloc(ba) {}
+ AC_Buffer* Convert();
+
+private:
+ // Return the size in byte needed to to save the specified state.
+ uint32 Calc_State_Sz(const ACS_State *) const;
+
+ // In fast-AC-graph, the ID is bit trikcy. Given a state of slow-graph,
+ // this function is to return the ID of its counterpart in the fast-graph.
+ State_ID Get_Renumbered_Id(const ACS_State *s) const {
+ const vector<uint32> &m = _id_map;
+ return m[s->Get_ID()];
+ }
+
+ AC_Buffer* Alloc_Buffer();
+ void Populate_Root_Goto_Func(AC_Buffer *, GotoVect&);
+
+#ifdef DEBUG
+ void dump_buffer(AC_Buffer*, FILE*);
+#endif
+
+private:
+ ACS_Constructor& _acs;
+ Buf_Allocator& _buf_alloc;
+
+ // map: ID of state in slow-graph -> ID of counterpart in fast-graph.
+ vector<uint32> _id_map;
+
+ // map: ID of state in slow-graph -> offset of counterpart in fast-graph.
+ vector<AC_Ofst> _ofst_map;
+};
+
+ac_result_t Match(AC_Buffer* buf, const char* str, uint32 len);
+ac_result_t Match_Longest_L(AC_Buffer* buf, const char* str, uint32 len);
+
+#endif // AC_FAST_H
diff --git a/modules/policy/lua-aho-corasick/ac_lua.cxx b/modules/policy/lua-aho-corasick/ac_lua.cxx
new file mode 100644
index 0000000..ad7307e
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/ac_lua.cxx
@@ -0,0 +1,173 @@
+// Interface functions for libac.so
+//
+#include <vector>
+#include <string>
+#include "ac_slow.hpp"
+#include "ac_fast.hpp"
+#include "ac.h" // for the definition of ac_result_t
+#include "ac_util.hpp"
+
+extern "C" {
+ #include <lua.h>
+ #include <lauxlib.h>
+}
+
+#if defined(USE_SLOW_VER)
+#error "Not going to implement it"
+#endif
+
+using namespace std;
+static const char* tname = "aho-corasick";
+
+class BufAlloc : public Buf_Allocator {
+public:
+ BufAlloc(lua_State* L) : _L(L) {}
+ virtual AC_Buffer* alloc(int sz) {
+ return (AC_Buffer*)lua_newuserdata (_L, sz);
+ }
+
+ // Let GC to take care.
+ virtual void free() {}
+
+private:
+ lua_State* _L;
+};
+
+static bool
+_create_helper(lua_State* L, const vector<const char*>& str_v,
+ const vector<unsigned int>& strlen_v) {
+ ASSERT(str_v.size() == strlen_v.size());
+
+ ACS_Constructor acc;
+ BufAlloc ba(L);
+
+ // Step 1: construt the slow version.
+ unsigned int strnum = str_v.size();
+ const char** str_vect = new const char*[strnum];
+ unsigned int* strlen_vect = new unsigned int[strnum];
+
+ int idx = 0;
+ for (vector<const char*>::const_iterator i = str_v.begin(), e = str_v.end();
+ i != e; i++) {
+ str_vect[idx++] = *i;
+ }
+
+ idx = 0;
+ for (vector<unsigned int>::const_iterator i = strlen_v.begin(),
+ e = strlen_v.end(); i != e; i++) {
+ strlen_vect[idx++] = *i;
+ }
+
+ acc.Construct(str_vect, strlen_vect, idx);
+ delete[] str_vect;
+ delete[] strlen_vect;
+
+ // Step 2: convert to fast version
+ AC_Converter cvt(acc, ba);
+ return cvt.Convert() != 0;
+}
+
+static ac_result_t
+_match_helper(buf_header_t* ac, const char *str, unsigned int len) {
+ AC_Buffer* buf = (AC_Buffer*)(void*)ac;
+ ASSERT(ac->magic_num == AC_MAGIC_NUM);
+
+ ac_result_t r = Match(buf, str, len);
+ return r;
+}
+
+// LUA sematic:
+// input: array of strings
+// output: userdata containing the AC-graph (i.e. the AC_Buffer).
+//
+static int
+lac_create(lua_State* L) {
+ // The table of the array must be the 1st argument.
+ int input_tab = 1;
+
+ luaL_checktype(L, input_tab, LUA_TTABLE);
+
+ // Init the "iteartor".
+ lua_pushnil(L);
+
+ vector<const char*> str_v;
+ vector<unsigned int> strlen_v;
+
+ // Loop over the elements
+ while (lua_next(L, input_tab)) {
+ size_t str_len;
+ const char* s = luaL_checklstring(L, -1, &str_len);
+ str_v.push_back(s);
+ strlen_v.push_back(str_len);
+
+ // remove the value, but keep the key as the iterator.
+ lua_pop(L, 1);
+ }
+
+ // pop the nil value
+ lua_pop(L, 1);
+
+ if (_create_helper(L, str_v, strlen_v)) {
+ // The AC graph, as a userdata is already pushed to the stack, hence 1.
+ return 1;
+ }
+
+ return 0;
+}
+
+// LUA input:
+// arg1: the userdata, representing the AC graph, returned from l_create().
+// arg2: the string to be matched.
+//
+// LUA return:
+// if match, return index range of the match; otherwise nil is returned.
+//
+static int
+lac_match(lua_State* L) {
+ buf_header_t* ac = (buf_header_t*)lua_touserdata(L, 1);
+ if (!ac) {
+ luaL_checkudata(L, 1, tname);
+ return 0;
+ }
+
+ size_t len;
+ const char* str;
+ #if LUA_VERSION_NUM >= 502
+ str = luaL_tolstring(L, 2, &len);
+ #else
+ str = lua_tolstring(L, 2, &len);
+ #endif
+ if (!str) {
+ luaL_checkstring(L, 2);
+ return 0;
+ }
+
+ ac_result_t r = _match_helper(ac, str, len);
+ if (r.match_begin != -1) {
+ lua_pushinteger(L, r.match_begin);
+ lua_pushinteger(L, r.match_end);
+ return 2;
+ }
+
+ return 0;
+}
+
+static const struct luaL_Reg lib_funcs[] = {
+ { "create", lac_create },
+ { "match", lac_match },
+ {0, 0}
+};
+
+extern "C" int AC_EXPORT
+luaopen_ahocorasick(lua_State* L) {
+ luaL_newmetatable(L, tname);
+
+#if LUA_VERSION_NUM == 501
+ luaL_register(L, tname, lib_funcs);
+#elif LUA_VERSION_NUM >= 502
+ luaL_newlib(L, lib_funcs);
+#else
+ #error "Don't know how to do it right"
+#endif
+ return 1;
+}
diff --git a/modules/policy/lua-aho-corasick/ac_slow.cxx b/modules/policy/lua-aho-corasick/ac_slow.cxx
new file mode 100644
index 0000000..cb3957a
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/ac_slow.cxx
@@ -0,0 +1,318 @@
+#include <ctype.h>
+#include <strings.h> // for bzero
+#include <algorithm>
+#include "ac_slow.hpp"
+#include "ac.h"
+
+//////////////////////////////////////////////////////////////////////////
+//
+// Implementation of AhoCorasick_Slow
+//
+//////////////////////////////////////////////////////////////////////////
+//
+ACS_Constructor::ACS_Constructor() : _next_node_id(1) {
+ _root = new_state();
+ _root_char = new InputTy[256];
+ bzero((void*)_root_char, 256);
+
+#ifdef VERIFY
+ _pattern_buf = 0;
+#endif
+}
+
+ACS_Constructor::~ACS_Constructor() {
+ for (std::vector<ACS_State* >::iterator i = _all_states.begin(),
+ e = _all_states.end(); i != e; i++) {
+ delete *i;
+ }
+ _all_states.clear();
+ delete[] _root_char;
+
+#ifdef VERIFY
+ delete[] _pattern_buf;
+#endif
+}
+
+ACS_State*
+ACS_Constructor::new_state() {
+ ACS_State* t = new ACS_State(_next_node_id++);
+ _all_states.push_back(t);
+ return t;
+}
+
+void
+ACS_Constructor::Add_Pattern(const char* str, unsigned int str_len,
+ int pattern_idx) {
+ ACS_State* state = _root;
+ for (unsigned int i = 0; i < str_len; i++) {
+ const char c = str[i];
+ ACS_State* new_s = state->Get_Goto(c);
+ if (!new_s) {
+ new_s = new_state();
+ new_s->_depth = state->_depth + 1;
+ state->Set_Goto(c, new_s);
+ }
+ state = new_s;
+ }
+ state->_is_terminal = true;
+ state->set_Pattern_Idx(pattern_idx);
+}
+
+void
+ACS_Constructor::Propagate_faillink() {
+ ACS_State* r = _root;
+ std::vector<ACS_State*> wl;
+
+ const ACS_Goto_Map& m = r->Get_Goto_Map();
+ for (ACS_Goto_Map::const_iterator i = m.begin(), e = m.end(); i != e; i++) {
+ ACS_State* s = i->second;
+ s->_fail_link = r;
+ wl.push_back(s);
+ }
+
+ // For any input c, make sure "goto(root, c)" is valid, which make the
+ // fail-link propagation lot easier.
+ ACS_Goto_Map goto_save = r->_goto_map;
+ for (uint32 i = 0; i <= 255; i++) {
+ ACS_State* s = r->Get_Goto(i);
+ if (!s) r->Set_Goto(i, r);
+ }
+
+ for (uint32 i = 0; i < wl.size(); i++) {
+ ACS_State* s = wl[i];
+ ACS_State* fl = s->_fail_link;
+
+ const ACS_Goto_Map& tran_map = s->Get_Goto_Map();
+
+ for (ACS_Goto_Map::const_iterator ii = tran_map.begin(),
+ ee = tran_map.end(); ii != ee; ii++) {
+ InputTy c = ii->first;
+ ACS_State *tran = ii->second;
+
+ ACS_State* tran_fl = 0;
+ for (ACS_State* fl_walk = fl; ;) {
+ if (ACS_State* t = fl_walk->Get_Goto(c)) {
+ tran_fl = t;
+ break;
+ } else {
+ fl_walk = fl_walk->Get_FailLink();
+ }
+ }
+
+ tran->_fail_link = tran_fl;
+ wl.push_back(tran);
+ }
+ }
+
+ // Remove "goto(root, c) == root" transitions
+ r->_goto_map = goto_save;
+}
+
+void
+ACS_Constructor::Construct(const char** strv, unsigned int* strlenv,
+ uint32 strnum) {
+ Save_Patterns(strv, strlenv, strnum);
+
+ for (uint32 i = 0; i < strnum; i++) {
+ Add_Pattern(strv[i], strlenv[i], i);
+ }
+
+ Propagate_faillink();
+ unsigned char* p = _root_char;
+
+ const ACS_Goto_Map& m = _root->Get_Goto_Map();
+ for (ACS_Goto_Map::const_iterator i = m.begin(), e = m.end();
+ i != e; i++) {
+ p[i->first] = 1;
+ }
+}
+
+Match_Result
+ACS_Constructor::MatchHelper(const char *str, uint32 len) const {
+ const ACS_State* root = _root;
+ const ACS_State* state = root;
+
+ uint32 idx = 0;
+ while (idx < len) {
+ InputTy c = str[idx];
+ idx++;
+ if (_root_char[c]) {
+ state = root->Get_Goto(c);
+ break;
+ }
+ }
+
+ if (unlikely(state->is_Terminal())) {
+ // This could happen if the one of the pattern has only one char!
+ uint32 pos = idx - 1;
+ Match_Result r(pos - state->Get_Depth() + 1, pos,
+ state->get_Pattern_Idx());
+ return r;
+ }
+
+ while (idx < len) {
+ InputTy c = str[idx];
+ ACS_State* gs = state->Get_Goto(c);
+
+ if (!gs) {
+ ACS_State* fl = state->Get_FailLink();
+ if (fl == root) {
+ while (idx < len) {
+ InputTy c = str[idx];
+ idx++;
+ if (_root_char[c]) {
+ state = root->Get_Goto(c);
+ break;
+ }
+ }
+ } else {
+ state = fl;
+ }
+ } else {
+ idx ++;
+ state = gs;
+ }
+
+ if (state->is_Terminal()) {
+ uint32 pos = idx - 1;
+ Match_Result r = Match_Result(pos - state->Get_Depth() + 1, pos,
+ state->get_Pattern_Idx());
+ return r;
+ }
+ }
+
+ return Match_Result(-1, -1, -1);
+}
+
+#ifdef DEBUG
+void
+ACS_Constructor::dump_text(const char* txtfile) const {
+ FILE* f = fopen(txtfile, "w+");
+ for (std::vector<ACS_State*>::const_iterator i = _all_states.begin(),
+ e = _all_states.end(); i != e; i++) {
+ ACS_State* s = *i;
+
+ fprintf(f, "S%d goto:{", s->Get_ID());
+ const ACS_Goto_Map& goto_func = s->Get_Goto_Map();
+
+ for (ACS_Goto_Map::const_iterator i = goto_func.begin(), e = goto_func.end();
+ i != e; i++) {
+ InputTy input = i->first;
+ ACS_State* tran = i->second;
+ if (isprint(input))
+ fprintf(f, "'%c' -> S:%d,", input, tran->Get_ID());
+ else
+ fprintf(f, "%#x -> S:%d,", input, tran->Get_ID());
+ }
+ fprintf(f, "} ");
+
+ if (s->_fail_link) {
+ fprintf(f, ", fail=S:%d", s->_fail_link->Get_ID());
+ }
+
+ if (s->_is_terminal) {
+ fprintf(f, ", terminal");
+ }
+
+ fprintf(f, "\n");
+ }
+ fclose(f);
+}
+
+void
+ACS_Constructor::dump_dot(const char *dotfile) const {
+ FILE* f = fopen(dotfile, "w+");
+ const char* indent = " ";
+
+ fprintf(f, "digraph G {\n");
+
+ // Emit node information
+ fprintf(f, "%s%d [style=filled];\n", indent, _root->Get_ID());
+ for (std::vector<ACS_State*>::const_iterator i = _all_states.begin(),
+ e = _all_states.end(); i != e; i++) {
+ ACS_State *s = *i;
+ if (s->_is_terminal) {
+ fprintf(f, "%s%d [shape=doublecircle];\n", indent, s->Get_ID());
+ }
+ }
+ fprintf(f, "\n");
+
+ // Emit edge information
+ for (std::vector<ACS_State*>::const_iterator i = _all_states.begin(),
+ e = _all_states.end(); i != e; i++) {
+ ACS_State* s = *i;
+ uint32 id = s->Get_ID();
+
+ const ACS_Goto_Map& m = s->Get_Goto_Map();
+ for (ACS_Goto_Map::const_iterator ii = m.begin(), ee = m.end();
+ ii != ee; ii++) {
+ InputTy input = ii->first;
+ ACS_State* tran = ii->second;
+ if (isalnum(input))
+ fprintf(f, "%s%d -> %d [label=%c];\n",
+ indent, id, tran->Get_ID(), input);
+ else
+ fprintf(f, "%s%d -> %d [label=\"%#x\"];\n",
+ indent, id, tran->Get_ID(), input);
+
+ }
+
+ // Emit fail-link
+ ACS_State* fl = s->Get_FailLink();
+ if (fl && fl != _root) {
+ fprintf(f, "%s%d -> %d [style=dotted, color=red]; \n",
+ indent, id, fl->Get_ID());
+ }
+ }
+ fprintf(f, "}\n");
+ fclose(f);
+}
+#endif
+
+#ifdef VERIFY
+void
+ACS_Constructor::Verify_Result(const char* subject, const Match_Result* r)
+ const {
+ if (r->begin >= 0) {
+ unsigned len = r->end - r->begin + 1;
+ int ptn_idx = r->pattern_idx;
+
+ ASSERT(ptn_idx >= 0 &&
+ len == get_ith_Pattern_Len(ptn_idx) &&
+ memcmp(subject + r->begin, get_ith_Pattern(ptn_idx), len) == 0);
+ }
+}
+
+void
+ACS_Constructor::Save_Patterns(const char** strv, unsigned int* strlenv,
+ int pattern_num) {
+ // calculate the total size needed to save all patterns.
+ //
+ int buf_size = 0;
+ for (int i = 0; i < pattern_num; i++) { buf_size += strlenv[i]; }
+
+ // HINT: patterns are delimited by '\0' in order to ease debugging.
+ buf_size += pattern_num;
+ ASSERT(_pattern_buf == 0);
+ _pattern_buf = new char[buf_size + 1];
+ #define MAGIC_NUM 0x5a
+ _pattern_buf[buf_size] = MAGIC_NUM;
+
+ int ofst = 0;
+ _pattern_lens.resize(pattern_num);
+ _pattern_vect.resize(pattern_num);
+ for (int i = 0; i < pattern_num; i++) {
+ int l = strlenv[i];
+ _pattern_lens[i] = l;
+ _pattern_vect[i] = _pattern_buf + ofst;
+
+ memcpy(_pattern_buf + ofst, strv[i], l);
+ ofst += l;
+ _pattern_buf[ofst++] = '\0';
+ }
+
+ ASSERT(_pattern_buf[buf_size] == MAGIC_NUM);
+ #undef MAGIC_NUM
+}
+
+#endif
diff --git a/modules/policy/lua-aho-corasick/ac_slow.hpp b/modules/policy/lua-aho-corasick/ac_slow.hpp
new file mode 100644
index 0000000..030b95d
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/ac_slow.hpp
@@ -0,0 +1,158 @@
+#ifndef MY_AC_H
+#define MY_AC_H
+
+#include <string.h>
+#include <stdio.h>
+#include <map>
+#include <vector>
+#include <algorithm> // for std::sort
+#include "ac_util.hpp"
+
+// Forward decl. the acronym "ACS" stands for "Aho-Corasick Slow implementation"
+class ACS_State;
+class ACS_Constructor;
+class AhoCorasick;
+
+using namespace std;
+
+typedef std::map<InputTy, ACS_State*> ACS_Goto_Map;
+
+class Match_Result {
+public:
+ int begin;
+ int end;
+ int pattern_idx;
+ Match_Result(int b, int e, int p): begin(b), end(e), pattern_idx(p) {}
+};
+
+typedef pair<InputTy, ACS_State *> GotoPair;
+typedef vector<GotoPair> GotoVect;
+
+// Sorting functor
+class GotoSort {
+public:
+ bool operator() (const GotoPair& g1, const GotoPair& g2) {
+ return g1.first < g2.first;
+ }
+};
+
+class ACS_State {
+friend class ACS_Constructor;
+
+public:
+ ACS_State(uint32 id): _id(id), _pattern_idx(-1), _depth(0),
+ _is_terminal(false), _fail_link(0){}
+ ~ACS_State() {};
+
+ void Set_Goto(InputTy c, ACS_State* s) { _goto_map[c] = s; }
+ ACS_State *Get_Goto(InputTy c) const {
+ ACS_Goto_Map::const_iterator iter = _goto_map.find(c);
+ return iter != _goto_map.end() ? (*iter).second : 0;
+ }
+
+ // Return all transitions sorted in the ascending order of their input.
+ void Get_Sorted_Gotos(GotoVect& Gotos) const {
+ const ACS_Goto_Map& m = _goto_map;
+ Gotos.clear();
+ for (ACS_Goto_Map::const_iterator i = m.begin(), e = m.end();
+ i != e; i++) {
+ Gotos.push_back(GotoPair(i->first, i->second));
+ }
+ sort(Gotos.begin(), Gotos.end(), GotoSort());
+ }
+
+ ACS_State* Get_FailLink() const { return _fail_link; }
+ uint32 Get_GotoNum() const { return _goto_map.size(); }
+ uint32 Get_ID() const { return _id; }
+ uint32 Get_Depth() const { return _depth; }
+ const ACS_Goto_Map& Get_Goto_Map(void) const { return _goto_map; }
+ bool is_Terminal() const { return _is_terminal; }
+ int get_Pattern_Idx() const {
+ ASSERT(is_Terminal() && _pattern_idx >= 0);
+ return _pattern_idx;
+ }
+
+private:
+ void set_Pattern_Idx(int idx) {
+ ASSERT(is_Terminal());
+ _pattern_idx = idx;
+ }
+
+private:
+ uint32 _id;
+ int _pattern_idx;
+ short _depth;
+ bool _is_terminal;
+ ACS_Goto_Map _goto_map;
+ ACS_State* _fail_link;
+};
+
+class ACS_Constructor {
+public:
+ ACS_Constructor();
+ ~ACS_Constructor();
+
+ void Construct(const char** strv, unsigned int* strlenv,
+ unsigned int strnum);
+
+ Match_Result Match(const char* s, uint32 len) const {
+ Match_Result r = MatchHelper(s, len);
+ Verify_Result(s, &r);
+ return r;
+ }
+
+ Match_Result Match(const char* s) const { return Match(s, strlen(s)); }
+
+#ifdef DEBUG
+ void dump_text(const char* = "ac.txt") const;
+ void dump_dot(const char* = "ac.dot") const;
+#endif
+ const ACS_State *Get_Root_State() const { return _root; }
+ const vector<ACS_State*>& Get_All_States() const {
+ return _all_states;
+ }
+
+ uint32 Get_Next_Node_Id() const { return _next_node_id; }
+ uint32 Get_State_Num() const { return _next_node_id - 1; }
+
+private:
+ void Add_Pattern(const char* str, unsigned int str_len, int pattern_idx);
+ ACS_State* new_state();
+ void Propagate_faillink();
+
+ Match_Result MatchHelper(const char*, uint32 len) const;
+
+#ifdef VERIFY
+ void Verify_Result(const char* subject, const Match_Result* r) const;
+ void Save_Patterns(const char** strv, unsigned int* strlenv, int vect_len);
+ const char* get_ith_Pattern(unsigned i) const {
+ ASSERT(i < _pattern_vect.size());
+ return _pattern_vect.at(i);
+ }
+ unsigned get_ith_Pattern_Len(unsigned i) const {
+ ASSERT(i < _pattern_lens.size());
+ return _pattern_lens.at(i);
+ }
+#else
+ void Verify_Result(const char* subject, const Match_Result* r) const {
+ (void)subject; (void)r;
+ }
+ void Save_Patterns(const char** strv, unsigned int* strlenv, int vect_len) {
+ (void)strv; (void)strlenv;
+ }
+#endif
+
+private:
+ ACS_State* _root;
+ vector<ACS_State*> _all_states;
+ unsigned char* _root_char;
+ uint32 _next_node_id;
+
+#ifdef VERIFY
+ char* _pattern_buf;
+ vector<int> _pattern_lens;
+ vector<char*> _pattern_vect;
+#endif
+};
+
+#endif
diff --git a/modules/policy/lua-aho-corasick/ac_util.hpp b/modules/policy/lua-aho-corasick/ac_util.hpp
new file mode 100644
index 0000000..56fd46c
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/ac_util.hpp
@@ -0,0 +1,69 @@
+/*
+ Copyright (c) 2014 CloudFlare, Inc. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following disclaimer
+ in the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of CloudFlare, Inc. nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+#ifndef AC_UTIL_H
+#define AC_UTIL_H
+
+#ifdef DEBUG
+#include <stdio.h> // for fprintf
+#include <stdlib.h> // for abort
+#endif
+
+typedef unsigned short uint16;
+typedef unsigned int uint32;
+typedef unsigned long uint64;
+typedef unsigned char InputTy;
+
+#ifdef DEBUG
+ // Usage examples: ASSERT(a > b), ASSERT(foo() && "Opps, foo() reutrn 0");
+ #define ASSERT(c) if (!(c))\
+ { fprintf(stderr, "%s:%d Assert: %s\n", __FILE__, __LINE__, #c); abort(); }
+#else
+ #define ASSERT(c) ((void)0)
+#endif
+
+#define likely(x) __builtin_expect((x),1)
+#define unlikely(x) __builtin_expect((x),0)
+
+#ifndef offsetof
+#define offsetof(st, m) ((size_t)(&((st *)0)->m))
+#endif
+
+typedef enum {
+ IMPL_SLOW_VARIANT = 1,
+ IMPL_FAST_VARIANT = 2,
+} impl_var_t;
+
+#define AC_MAGIC_NUM 0x5a
+typedef struct {
+ unsigned char magic_num;
+ unsigned char impl_variant;
+} buf_header_t;
+
+#endif //AC_UTIL_H
diff --git a/modules/policy/lua-aho-corasick/load_ac.lua b/modules/policy/lua-aho-corasick/load_ac.lua
new file mode 100644
index 0000000..eb70446
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/load_ac.lua
@@ -0,0 +1,90 @@
+-- Helper wrappring script for loading shared object libac.so (FFI interface)
+-- from package.cpath instead of LD_LIBRARTY_PATH.
+--
+
+local ffi = require 'ffi'
+ffi.cdef[[
+ void* ac_create(const char** str_v, unsigned int* strlen_v,
+ unsigned int v_len);
+ int ac_match2(void*, const char *str, int len);
+ void ac_free(void*);
+]]
+
+local _M = {}
+
+local string_gmatch = string.gmatch
+local string_match = string.match
+
+local ac_lib = nil
+local ac_create = nil
+local ac_match = nil
+local ac_free = nil
+
+--[[ Find shared object file package.cpath, obviating the need of setting
+ LD_LIBRARY_PATH
+]]
+local function find_shared_obj(cpath, so_name)
+ for k, v in string_gmatch(cpath, "[^;]+") do
+ local so_path = string_match(k, "(.*/)")
+ if so_path then
+ -- "so_path" could be nil. e.g, the dir path component is "."
+ so_path = so_path .. so_name
+
+ -- Don't get me wrong, the only way to know if a file exist is
+ -- trying to open it.
+ local f = io.open(so_path)
+ if f ~= nil then
+ io.close(f)
+ return so_path
+ end
+ end
+ end
+end
+
+function _M.load_ac_lib()
+ if ac_lib ~= nil then
+ return ac_lib
+ else
+ local so_path = find_shared_obj(package.cpath, "libac.so")
+ if so_path ~= nil then
+ ac_lib = ffi.load(so_path)
+ ac_create = ac_lib.ac_create
+ ac_match = ac_lib.ac_match2
+ ac_free = ac_lib.ac_free
+ return ac_lib
+ end
+ end
+end
+
+-- Create an Aho-Corasick instance, and return the instance if it was
+-- successful.
+function _M.create_ac(dict)
+ local strnum = #dict
+ if ac_lib == nil then
+ _M.load_ac_lib()
+ end
+
+ local str_v = ffi.new("const char *[?]", strnum)
+ local strlen_v = ffi.new("unsigned int [?]", strnum)
+
+ for i = 1, strnum do
+ local s = dict[i]
+ str_v[i - 1] = s
+ strlen_v[i - 1] = #s
+ end
+
+ local ac = ac_create(str_v, strlen_v, strnum);
+ if ac ~= nil then
+ return ffi.gc(ac, ac_free)
+ end
+end
+
+-- Return nil if str doesn't match the dictionary, else return non-nil.
+function _M.match(ac, str)
+ local r = ac_match(ac, str, #str);
+ if r >= 0 then
+ return r
+ end
+end
+
+return _M
diff --git a/modules/policy/lua-aho-corasick/mytest.cxx b/modules/policy/lua-aho-corasick/mytest.cxx
new file mode 100644
index 0000000..ef3dc87
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/mytest.cxx
@@ -0,0 +1,200 @@
+#include <stdio.h>
+#include <string.h>
+#include <vector>
+#include "ac.h"
+
+using namespace std;
+
+/////////////////////////////////////////////////////////////////////////
+//
+// Test using strings from input files
+//
+/////////////////////////////////////////////////////////////////////////
+//
+class BigFileTester {
+public:
+ BigFileTester(const char* filepath);
+
+private:
+ void Genector
+privaete:
+ const char* _msg;
+ int _msg_len;
+ int _key_num; // number of strings in dictionary
+ int _key_len_idx;
+};
+
+/////////////////////////////////////////////////////////////////////////
+//
+// Simple (yet maybe tricky) testings
+//
+/////////////////////////////////////////////////////////////////////////
+//
+typedef struct {
+ const char* str;
+ const char* match;
+} StrPair;
+
+typedef struct {
+ const char* name;
+ const char** dict;
+ StrPair* strpairs;
+ int dict_len;
+ int strpair_num;
+} TestingCase;
+
+class Tests {
+public:
+ Tests(const char* name,
+ const char* dict[], int dict_len,
+ StrPair strpairs[], int strpair_num) {
+ if (!_tests)
+ _tests = new vector<TestingCase>;
+
+ TestingCase tc;
+ tc.name = name;
+ tc.dict = dict;
+ tc.strpairs = strpairs;
+ tc.dict_len = dict_len;
+ tc.strpair_num = strpair_num;
+ _tests->push_back(tc);
+ }
+
+ static vector<TestingCase>* Get_Tests() { return _tests; }
+ static void Erase_Tests() { delete _tests; _tests = 0; }
+
+private:
+ static vector<TestingCase> *_tests;
+};
+
+vector<TestingCase>* Tests::_tests = 0;
+
+static void
+simple_test(void) {
+ int total = 0;
+ int fail = 0;
+
+ vector<TestingCase> *tests = Tests::Get_Tests();
+ if (!tests)
+ return 0;
+
+ for (vector<TestingCase>::iterator i = tests->begin(), e = tests->end();
+ i != e; i++) {
+ TestingCase& t = *i;
+ fprintf(stdout, ">Testing %s\nDictionary:[ ", t.name);
+ for (int i = 0, e = t.dict_len, need_break=0; i < e; i++) {
+ fprintf(stdout, "%s, ", t.dict[i]);
+ if (need_break++ == 16) {
+ fputs("\n ", stdout);
+ need_break = 0;
+ }
+ }
+ fputs("]\n", stdout);
+
+ /* Create the dictionary */
+ int dict_len = t.dict_len;
+ ac_t* ac = ac_create(t.dict, dict_len);
+
+ for (int ii = 0, ee = t.strpair_num; ii < ee; ii++, total++) {
+ const StrPair& sp = t.strpairs[ii];
+ const char *str = sp.str; // the string to be matched
+ const char *match = sp.match;
+
+ fprintf(stdout, "[%3d] Testing '%s' : ", total, str);
+
+ int len = strlen(str);
+ ac_result_t r = ac_match(ac, str, len);
+ int m_b = r.match_begin;
+ int m_e = r.match_end;
+
+ // The return value per se is insane.
+ if (m_b > m_e ||
+ ((m_b < 0 || m_e < 0) && (m_b != -1 || m_e != -1))) {
+ fprintf(stdout, "Insane return value (%d, %d)\n", m_b, m_e);
+ fail ++;
+ continue;
+ }
+
+ // If the string is not supposed to match the dictionary.
+ if (!match) {
+ if (m_b != -1 || m_e != -1) {
+ fail ++;
+ fprintf(stdout, "Not Supposed to match (%d, %d) \n",
+ m_b, m_e);
+ } else
+ fputs("Pass\n", stdout);
+ continue;
+ }
+
+ // The string or its substring is match the dict.
+ if (m_b >= len || m_b >= len) {
+ fail ++;
+ fprintf(stdout,
+ "Return value >= the length of the string (%d, %d)\n",
+ m_b, m_e);
+ continue;
+ } else {
+ int mlen = strlen(match);
+ if ((mlen != m_e - m_b + 1) ||
+ strncmp(str + m_b, match, mlen)) {
+ fail ++;
+ fprintf(stdout, "Fail\n");
+ } else
+ fprintf(stdout, "Pass\n");
+ }
+ }
+ fputs("\n", stdout);
+ ac_free(ac);
+ }
+
+ fprintf(stdout, "Total : %d, Fail %d\n", total, fail);
+
+ return fail ? -1 : 0;
+}
+
+int
+main (int argc, char** argv) {
+ int res = simple_test();
+ return res;
+};
+
+/* test 1*/
+const char *dict1[] = {"he", "she", "his", "her"};
+StrPair strpair1[] = {
+ {"he", "he"}, {"she", "she"}, {"his", "his"},
+ {"hers", "he"}, {"ahe", "he"}, {"shhe", "he"},
+ {"shis2", "his"}, {"ahhe", "he"}
+};
+Tests test1("test 1",
+ dict1, sizeof(dict1)/sizeof(dict1[0]),
+ strpair1, sizeof(strpair1)/sizeof(strpair1[0]));
+
+/* test 2*/
+const char *dict2[] = {"poto", "poto"}; /* duplicated strings*/
+StrPair strpair2[] = {{"The pot had a handle", 0}};
+Tests test2("test 2", dict2, 2, strpair2, 1);
+
+/* test 3*/
+const char *dict3[] = {"The"};
+StrPair strpair3[] = {{"The pot had a handle", "The"}};
+Tests test3("test 3", dict3, 1, strpair3, 1);
+
+/* test 4*/
+const char *dict4[] = {"pot"};
+StrPair strpair4[] = {{"The pot had a handle", "pot"}};
+Tests test4("test 4", dict4, 1, strpair4, 1);
+
+/* test 5*/
+const char *dict5[] = {"pot "};
+StrPair strpair5[] = {{"The pot had a handle", "pot "}};
+Tests test5("test 5", dict5, 1, strpair5, 1);
+
+/* test 6*/
+const char *dict6[] = {"ot h"};
+StrPair strpair6[] = {{"The pot had a handle", "ot h"}};
+Tests test6("test 6", dict6, 1, strpair6, 1);
+
+/* test 7*/
+const char *dict7[] = {"andle"};
+StrPair strpair7[] = {{"The pot had a handle", "andle"}};
+Tests test7("test 7", dict7, 1, strpair7, 1);
diff --git a/modules/policy/lua-aho-corasick/tests/Makefile b/modules/policy/lua-aho-corasick/tests/Makefile
new file mode 100644
index 0000000..54fd90f
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/tests/Makefile
@@ -0,0 +1,65 @@
+OS := $(shell uname)
+ifeq ($(OS), Darwin)
+ SO_EXT := dylib
+else
+ SO_EXT := so
+endif
+
+.PHONY = all clean test runtest benchmark
+
+PROGRAM = ac_test
+BENCHMARK = ac_bench
+all: runtest
+
+CXXFLAGS = -O3 -g -march=native -Wall -DDEBUG
+MYCXXFLAGS = -MMD -I.. $(CXXFLAGS)
+%.o : %.cxx
+ $(CXX) $< -c $(MYCXXFLAGS)
+
+-include dep.cxx
+SRC = test_main.cxx ac_test_simple.cxx ac_test_aggr.cxx test_bigfile.cxx
+
+OBJ = ${SRC:.cxx=.o}
+
+-include test_dep.txt
+-include bench_dep.txt
+
+$(PROGRAM) $(BENCHMARK) : testinput/text.tar testinput/image.bin
+$(PROGRAM) : $(OBJ) ../libac.$(SO_EXT)
+ $(CXX) $(OBJ) -L.. -lac -o $@
+ -cat *.d > test_dep.txt
+
+$(BENCHMARK) : ac_bench.o ../libac.$(SO_EXT)
+ $(CXX) ac_bench.o -L.. -lac -o $@
+ -cat *.d > bench_dep.txt
+
+ifneq ($(OS), Darwin)
+runtest:$(PROGRAM)
+ LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):.. ./$(PROGRAM) testinput/*
+
+benchmark:$(BENCHMARK)
+ LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):.. ./ac_bench
+
+else
+runtest:$(PROGRAM)
+ DYLD_LIBRARY_PATH=$(DYLD_LIBRARY_PATH):.. ./$(PROGRAM) testinput/*
+
+benchmark:$(BENCHMARK)
+ DYLD_LIBRARY_PATH=$(DYLD_LIBRARY_PATH):.. ./ac_bench
+
+endif
+
+testinput/text.tar:
+ echo "download testing files (gcc tarball)..."
+ if [ ! -d testinput ] ; then mkdir testinput; fi
+ cd testinput && \
+ curl ftp://ftp.gnu.org/gnu/gcc/gcc-1.42.tar.gz -o text.tar.gz 2>/dev/null \
+ && gzip -d text.tar.gz
+
+testinput/image.bin:
+ echo "download testing files.."
+ if [ ! -d testinput ] ; then mkdir testinput; fi
+ curl http://www.3dvisionlive.com/sites/default/files/Curiosity_render_hiresb.jpg -o $@ 2>/dev/null
+
+clean:
+ -rm -f *.o *.d dep.txt $(PROGRAM) $(BENCHMARK)
diff --git a/modules/policy/lua-aho-corasick/tests/ac_bench.cxx b/modules/policy/lua-aho-corasick/tests/ac_bench.cxx
new file mode 100644
index 0000000..421322c
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/tests/ac_bench.cxx
@@ -0,0 +1,519 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+#include <time.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <libgen.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+
+#include <string>
+#include <vector>
+#include "ac.h"
+#include "ac_util.hpp"
+
+using namespace std;
+
+static bool SomethingWrong = false;
+
+static int iteration = 300;
+static string dict_dir;
+static string obj_file_dir;
+static bool print_help = false;
+static int piece_size = 1024;
+
+class PatternSet {
+public:
+ PatternSet(const char* filepath);
+ ~PatternSet() { Cleanup(); }
+
+ int getPatternNum() const { return _pat_num; }
+ const char** getPatternVector() const { return _patterns; }
+ unsigned int* getPatternLenVector() const { return _pat_len; }
+
+ const char* getErrMessage() const { return _errmsg; }
+ static bool isDictFile(const char* filepath) {
+ if (strncmp(basename(const_cast<char*>(filepath)), "dict", 4))
+ return false;
+ return true;
+ }
+
+private:
+ bool ExtractPattern(const char* filepath);
+ void Cleanup();
+
+ const char** _patterns;
+ unsigned int* _pat_len;
+ char* _mmap;
+ int _fd;
+ size_t _mmap_size;
+ int _pat_num;
+
+ const char* _errmsg;
+};
+
+bool
+PatternSet::ExtractPattern(const char* filepath) {
+ if (!isDictFile(filepath))
+ return false;
+
+ struct stat filestat;
+ if (stat(filepath, &filestat)) {
+ _errmsg = "fail to call stat()";
+ return false;
+ }
+
+ if (filestat.st_size > 4096 * 1024) {
+ /* It dosen't seem to be a dictionary file*/
+ _errmsg = "file too big?";
+ return false;
+ }
+
+ _fd = open(filepath, 0);
+ if (_fd == -1) {
+ _errmsg = "fail to open dictionary file";
+ return false;
+ }
+
+ _mmap_size = filestat.st_size;
+ _mmap = (char*)mmap(0, filestat.st_size, PROT_READ|PROT_WRITE,
+ MAP_PRIVATE, _fd, 0);
+ if (_mmap == MAP_FAILED) {
+ _errmsg = "fail to call mmap";
+ return false;
+ }
+
+ const char* pat = _mmap;
+ vector<const char*> pat_vect;
+ vector<unsigned> pat_len_vect;
+
+ for (size_t i = 0, e = filestat.st_size; i < e; i++) {
+ if (_mmap[i] == '\r' || _mmap[i] == '\n') {
+ _mmap[i] = '\0';
+ int len = _mmap + i - pat;
+ if (len > 0) {
+ pat_vect.push_back(pat);
+ pat_len_vect.push_back(len);
+ }
+ pat = _mmap + i + 1;
+ }
+ }
+
+ ASSERT(pat_vect.size() == pat_len_vect.size());
+
+ int pat_num = pat_vect.size();
+ if (pat_num > 0) {
+ const char** p = _patterns = new const char*[pat_num];
+ int i = 0;
+ for (vector<const char*>::iterator iter = pat_vect.begin(),
+ iter_e = pat_vect.end(); iter != iter_e; ++iter) {
+ p[i++] = *iter;
+ }
+
+ i = 0;
+ unsigned int* q = _pat_len = new unsigned int[pat_num];
+ for (vector<unsigned>::iterator iter = pat_len_vect.begin(),
+ iter_e = pat_len_vect.end(); iter != iter_e; ++iter) {
+ q[i++] = *iter;
+ }
+ }
+
+ _pat_num = pat_num;
+ if (pat_num <= 0) {
+ _errmsg = "no pattern at all";
+ return false;
+ }
+
+ return true;
+}
+
+void
+PatternSet::Cleanup() {
+ if (_mmap != MAP_FAILED) {
+ munmap(_mmap, _mmap_size);
+ _mmap = (char*)MAP_FAILED;
+ _mmap_size = 0;
+ }
+
+ delete[] _patterns;
+ delete[] _pat_len;
+ if (_fd != -1)
+ close(_fd);
+ _pat_num = -1;
+}
+
+PatternSet::PatternSet(const char* filepath) {
+ _patterns = 0;
+ _pat_len = 0;
+ _mmap = (char*)MAP_FAILED;
+ _mmap_size = 0;
+ _pat_num = -1;
+ _errmsg = "";
+
+ if (!ExtractPattern(filepath))
+ Cleanup();
+}
+
+bool
+getFilesUnderDir(vector<string>& files, const char* path) {
+ files.clear();
+
+ DIR* dir = opendir(path);
+ if (!dir)
+ return false;
+
+ string path_dir = path;
+ path_dir += "/";
+
+ for (;;) {
+ struct dirent* entry = readdir(dir);
+ if (entry) {
+ string filepath = path_dir + entry->d_name;
+ struct stat file_stat;
+ if (stat(filepath.c_str(), &file_stat)) {
+ closedir(dir);
+ return false;
+ }
+
+ if (S_ISREG(file_stat.st_mode))
+ files.push_back(filepath);
+
+ continue;
+ }
+
+ if (errno) {
+ return false;
+ }
+ break;
+ }
+ closedir(dir);
+ return true;
+}
+
+class Timer {
+public:
+ Timer() {
+ my_clock_gettime(&_start);
+ _stop = _start;
+ _acc.tv_sec = 0;
+ _acc.tv_nsec = 0;
+ }
+
+ const Timer& operator += (const Timer& that) {
+ time_t sec = _acc.tv_sec + that._acc.tv_sec;
+ long nsec = _acc.tv_nsec + that._acc.tv_nsec;
+ if (nsec > 1000000000) {
+ nsec -= 1000000000;
+ sec += 1;
+ }
+ _acc.tv_sec = sec;
+ _acc.tv_nsec = nsec;
+ return *this;
+ }
+
+ // return duration in us
+ size_t getDuration() const {
+ return _acc.tv_sec * (size_t)1000000 + _acc.tv_nsec/1000;
+ }
+
+ void Start(bool acc=true) {
+ my_clock_gettime(&_start);
+ }
+
+ void Stop() {
+ my_clock_gettime(&_stop);
+ struct timespec t = CalcDuration();
+ _acc = add_duration(_acc, t);
+ }
+
+private:
+ int my_clock_gettime(struct timespec* t) {
+#ifdef __linux
+ return clock_gettime(CLOCK_PROCESS_CPUTIME_ID, t);
+#else
+ struct timeval tv;
+ int rc = gettimeofday(&tv, 0);
+ t->tv_sec = tv.tv_sec;
+ t->tv_nsec = tv.tv_usec * 1000;
+ return rc;
+#endif
+ }
+
+ struct timespec add_duration(const struct timespec& dur1,
+ const struct timespec& dur2) {
+ time_t sec = dur1.tv_sec + dur2.tv_sec;
+ long nsec = dur1.tv_nsec + dur2.tv_nsec;
+ if (nsec > 1000000000) {
+ nsec -= 1000000000;
+ sec += 1;
+ }
+ timespec t;
+ t.tv_sec = sec;
+ t.tv_nsec = nsec;
+
+ return t;
+ }
+
+ struct timespec CalcDuration() const {
+ timespec diff;
+ if ((_stop.tv_nsec - _start.tv_nsec)<0) {
+ diff.tv_sec = _stop.tv_sec - _start.tv_sec - 1;
+ diff.tv_nsec = 1000000000 + _stop.tv_nsec - _start.tv_nsec;
+ } else {
+ diff.tv_sec = _stop.tv_sec - _start.tv_sec;
+ diff.tv_nsec = _stop.tv_nsec - _start.tv_nsec;
+ }
+ return diff;
+ }
+
+ struct timespec _start;
+ struct timespec _stop;
+ struct timespec _acc;
+};
+
+class Benchmark {
+public:
+ Benchmark(const PatternSet& pat_set, const char* infile):
+ _pat_set(pat_set), _infile(infile) {
+ _mmap = (char*)MAP_FAILED;
+ _file_sz = 0;
+ _fd = -1;
+ }
+
+ ~Benchmark() {
+ if (_mmap != MAP_FAILED)
+ munmap(_mmap, _file_sz);
+ if (_fd != -1)
+ close(_fd);
+ }
+
+ bool Run(int iteration);
+ const Timer& getTimer() const { return _timer; }
+
+private:
+ const PatternSet& _pat_set;
+ const char* _infile;
+ char* _mmap;
+ int _fd;
+ size_t _file_sz; // input file size
+ Timer _timer;
+};
+
+bool
+Benchmark::Run(int iteration) {
+ if (_pat_set.getPatternNum() <= 0) {
+ SomethingWrong = true;
+ return false;
+ }
+
+ if (_mmap == MAP_FAILED) {
+ struct stat filestat;
+ if (stat(_infile, &filestat)) {
+ SomethingWrong = true;
+ return false;
+ }
+
+ if (!S_ISREG(filestat.st_mode)) {
+ SomethingWrong = true;
+ return false;
+ }
+
+ _fd = open(_infile, 0);
+ if (_fd == -1)
+ return false;
+
+ _mmap = (char*)mmap(0, filestat.st_size, PROT_READ|PROT_WRITE,
+ MAP_PRIVATE, _fd, 0);
+
+ if (_mmap == MAP_FAILED) {
+ SomethingWrong = true;
+ return false;
+ }
+
+ _file_sz = filestat.st_size;
+ }
+
+ ac_t* ac = ac_create(_pat_set.getPatternVector(),
+ _pat_set.getPatternLenVector(),
+ _pat_set.getPatternNum());
+ if (!ac) {
+ SomethingWrong = true;
+ return false;
+ }
+
+ int piece_num = _file_sz/piece_size;
+
+ _timer.Start(false);
+
+ /* Stupid compiler may not be able to promote piece_size into register.
+ * Do it manually.
+ */
+ int piece_sz = piece_size;
+ for (int i = 0; i < iteration; i++) {
+ size_t match_ofst = 0;
+ for (int piece_idx = 0; piece_idx < piece_num; piece_idx ++) {
+ ac_match2(ac, _mmap + match_ofst, piece_sz);
+ match_ofst += piece_sz;
+ }
+ if (match_ofst != _file_sz)
+ ac_match2(ac, _mmap + match_ofst, _file_sz - match_ofst);
+ }
+ _timer.Stop();
+ return true;
+}
+
+const char* short_opt = "hd:f:i:p:";
+const struct option long_opts[] = {
+ {"help", no_argument, 0, 'h'},
+ {"iteration", required_argument, 0, 'i'},
+ {"dictionary-dir", required_argument, 0, 'd'},
+ {"obj-file-dir", required_argument, 0, 'f'},
+ {"piece-size", required_argument, 0, 'p'},
+};
+
+static void
+PrintHelp(const char* prog_name) {
+ const char* msg =
+"Usage %s [OPTIONS]\n"
+" -d, --dictionary-dir : specify the dictionary directory (./dict by default)\n"
+" -f, --obj-file-dir : specify the object file directory\n"
+" (./testinput by default)\n"
+" -i, --iteration : Run this many iteration for each pattern match\n"
+" -p, --piece-size : The size of 'piece' in byte. The input file is\n"
+" divided into pieces, and match function is working\n"
+" on one piece at a time. The default size of piece\n"
+" is 1k byte.\n";
+
+ fprintf(stdout, msg, prog_name);
+}
+
+static bool
+getOptions(int argc, char** argv) {
+ bool dict_dir_set = false;
+ bool objfile_dir_set = false;
+ int opt_index;
+
+ while (1) {
+ if (print_help) break;
+
+ int c = getopt_long(argc, argv, short_opt, long_opts, &opt_index);
+
+ if (c == -1) break;
+ if (c == 0) { c = long_opts[opt_index].val; }
+
+ switch(c) {
+ case 'h':
+ print_help = true;
+ break;
+
+ case 'i':
+ iteration = atol(optarg);
+ break;
+
+ case 'd':
+ dict_dir = optarg;
+ dict_dir_set = true;
+ break;
+
+ case 'f':
+ obj_file_dir = optarg;
+ objfile_dir_set = true;
+ break;
+
+ case 'p':
+ piece_size = atol(optarg);
+ break;
+
+ case '?':
+ default:
+ return false;
+ }
+ }
+
+ if (print_help)
+ return true;
+
+ string basedir(dirname(argv[0]));
+ if (!dict_dir_set)
+ dict_dir = basedir + "/dict";
+
+ if (!objfile_dir_set)
+ obj_file_dir = basedir + "/testinput";
+
+ return true;
+}
+
+int
+main(int argc, char** argv) {
+ if (!getOptions(argc, argv))
+ return -1;
+
+ if (print_help) {
+ PrintHelp(argv[0]);
+ return 0;
+ }
+
+#ifndef __linux
+ fprintf(stdout, "\n!!!WARNING: On this OS, the execution time is measured"
+ " by gettimeofday(2) which is imprecise!!!\n\n");
+#endif
+
+ fprintf(stdout, "Test with iteration = %d, piece size = %d, and",
+ iteration, piece_size);
+ fprintf(stdout, "\n dictionary dir = %s\n object file dir = %s\n\n",
+ dict_dir.c_str(), obj_file_dir.c_str());
+
+ vector<string> dict_files;
+ vector<string> input_files;
+
+ if (!getFilesUnderDir(dict_files, dict_dir.c_str())) {
+ fprintf(stdout, "fail to find dictionary files\n");
+ return -1;
+ }
+
+ if (!getFilesUnderDir(input_files, obj_file_dir.c_str())) {
+ fprintf(stdout, "fail to find test input files\n");
+ return -1;
+ }
+
+ for (vector<string>::iterator diter = dict_files.begin(),
+ diter_e = dict_files.end(); diter != diter_e; ++diter) {
+
+ const char* dict_name = diter->c_str();
+ if (!PatternSet::isDictFile(dict_name))
+ continue;
+
+ PatternSet ps(dict_name);
+ if (ps.getPatternNum() <= 0) {
+ fprintf(stdout, "fail to open dictionary file %s : %s\n",
+ dict_name, ps.getErrMessage());
+ SomethingWrong = true;
+ continue;
+ }
+
+ fprintf(stdout, "Using dictionary %s\n", dict_name);
+ Timer timer;
+ for (vector<string>::iterator iter = input_files.begin(),
+ iter_e = input_files.end(); iter != iter_e; ++iter) {
+ fprintf(stdout, " testing %s ... ", iter->c_str());
+ fflush(stdout);
+ Benchmark bm(ps, iter->c_str());
+ bm.Run(iteration);
+ const Timer& t = bm.getTimer();
+ timer += bm.getTimer();
+ fprintf(stdout, "elapsed %.3f\n", t.getDuration() / 1000000.0);
+ }
+
+ fprintf(stdout,
+ "\n==========================================================\n"
+ " Total Elapse %.3f\n\n", timer.getDuration() / 1000000.0);
+ }
+
+ return SomethingWrong ? -1 : 0;
+}
diff --git a/modules/policy/lua-aho-corasick/tests/ac_test_aggr.cxx b/modules/policy/lua-aho-corasick/tests/ac_test_aggr.cxx
new file mode 100644
index 0000000..4ea02bc
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/tests/ac_test_aggr.cxx
@@ -0,0 +1,135 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <vector>
+#include <string>
+
+#include "ac.h"
+#include "ac_util.hpp"
+#include "test_base.hpp"
+
+using namespace std;
+
+namespace {
+class ACBigFileTester : public BigFileTester {
+public:
+ ACBigFileTester(const char* filepath) : BigFileTester(filepath){};
+
+private:
+ virtual buf_header_t* PM_Create(const char** strv, uint32* strlenv,
+ uint32 vect_len) {
+ return (buf_header_t*)ac_create(strv, strlenv, vect_len);
+ }
+
+ virtual void PM_Free(buf_header_t* PM) { ac_free(PM); }
+ virtual bool Run_Helper(buf_header_t* PM);
+};
+
+class ACTestAggressive: public ACTestBase {
+public:
+ ACTestAggressive(const vector<const char*>& files, const char* banner)
+ : ACTestBase(banner), _files(files) {}
+ virtual bool Run();
+
+private:
+ void PrintSummary(int total, int fail) {
+ fprintf(stdout, "Test count : %d, fail: %d\n", total, fail);
+ fflush(stdout);
+ }
+ vector<const char*> _files;
+};
+
+} // end of anonymous namespace
+
+bool
+ACBigFileTester::Run_Helper(buf_header_t* PM) {
+ int fail = 0;
+ // advance one chunk at a time.
+ int len = _msg_len;
+ int chunk_sz = _chunk_sz;
+
+ vector<const char*> c_style_keys;
+ for (int i = 0, e = _keys.size(); i != e; i++) {
+ const char* key = _keys[i].first;
+ int len = _keys[i].second;
+ char *t = new char[len+1];
+ memcpy(t, key, len);
+ t[len] = '\0';
+ c_style_keys.push_back(t);
+ }
+
+ for (int ofst = 0, chunk_idx = 0, chunk_num = _chunk_num;
+ chunk_idx < chunk_num; ofst += chunk_sz, chunk_idx++) {
+ const char* substring = _msg + ofst;
+ ac_result_t r = ac_match((ac_t*)(void*)PM, substring , len - ofst);
+ int m_b = r.match_begin;
+ int m_e = r.match_end;
+
+ if (m_b < 0 || m_e < 0 || m_e <= m_b || m_e >= len) {
+ fprintf(stdout, "fail to find match substring[%d:%d])\n",
+ ofst, len - 1);
+ fail ++;
+ continue;
+ }
+
+ const char* match_str = _msg + len;
+ int strstr_len = 0;
+ int key_idx = -1;
+
+ for (int i = 0, e = c_style_keys.size(); i != e; i++) {
+ const char* key = c_style_keys[i];
+ if (const char *m = strstr(substring, key)) {
+ if (m < match_str) {
+ match_str = m;
+ strstr_len = _keys[i].second;
+ key_idx = i;
+ }
+ }
+ }
+ ASSERT(key_idx != -1);
+ if ((match_str - substring != m_b)) {
+ fprintf(stdout,
+ "Fail to find match substring[%d:%d]),"
+ " expected to find match at offset %d instead of %d\n",
+ ofst, len - 1,
+ (int)(match_str - _msg), ofst + m_b);
+ fprintf(stdout, "%d vs %d (key idx %d)\n", strstr_len, m_e - m_b + 1, key_idx);
+ PrintStr(stdout, match_str, strstr_len);
+ fprintf(stdout, "\n");
+ PrintStr(stdout, _msg + ofst + m_b,
+ m_e - m_b + 1);
+ fprintf(stdout, "\n");
+ fail ++;
+ }
+ }
+ for (vector<const char*>::iterator i = c_style_keys.begin(),
+ e = c_style_keys.end(); i != e; i++) {
+ delete[] *i;
+ }
+
+ return fail == 0;
+}
+
+bool
+ACTestAggressive::Run() {
+ int fail = 0;
+ for (vector<const char*>::iterator i = _files.begin(), e = _files.end();
+ i != e; i++) {
+ ACBigFileTester bft(*i);
+ if (!bft.Run())
+ fail ++;
+ }
+ return fail == 0;
+}
+
+bool
+Run_AC_Aggressive_Test(const vector<const char*>& files) {
+ ACTestAggressive t(files, "AC Aggressive test");
+ t.PrintBanner();
+ return t.Run();
+}
diff --git a/modules/policy/lua-aho-corasick/tests/ac_test_simple.cxx b/modules/policy/lua-aho-corasick/tests/ac_test_simple.cxx
new file mode 100644
index 0000000..fa2d7fd
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/tests/ac_test_simple.cxx
@@ -0,0 +1,275 @@
+#include <stdio.h>
+#include <string.h>
+#include <vector>
+#include <string>
+
+#include "ac.h"
+#include "ac_util.hpp"
+#include "test_base.hpp"
+
+using namespace std;
+
+namespace {
+typedef struct {
+ const char* str;
+ const char* match;
+} StrPair;
+
+typedef enum {
+ MV_FIRST_MATCH = 0,
+ MV_LEFT_LONGEST = 1,
+} MatchVariant;
+
+typedef struct {
+ const char* name;
+ const char** dict;
+ StrPair* strpairs;
+ int dict_len;
+ int strpair_num;
+ MatchVariant match_variant;
+} TestingCase;
+
+class Tests {
+public:
+ Tests(const char* name,
+ const char* dict[], int dict_len,
+ StrPair strpairs[], int strpair_num,
+ MatchVariant mv = MV_FIRST_MATCH) {
+ if (!_tests)
+ _tests = new vector<TestingCase>;
+
+ TestingCase tc;
+ tc.name = name;
+ tc.dict = dict;
+ tc.strpairs = strpairs;
+ tc.dict_len = dict_len;
+ tc.strpair_num = strpair_num;
+ tc.match_variant = mv;
+ _tests->push_back(tc);
+ }
+
+ static vector<TestingCase>* Get_Tests() { return _tests; }
+ static void Erase_Tests() { delete _tests; _tests = 0; }
+
+private:
+ static vector<TestingCase> *_tests;
+};
+
+class LeftLongestTests : public Tests {
+public:
+ LeftLongestTests (const char* name, const char* dict[], int dict_len,
+ StrPair strpairs[], int strpair_num):
+ Tests(name, dict, dict_len, strpairs, strpair_num, MV_LEFT_LONGEST) {
+ }
+};
+
+vector<TestingCase>* Tests::_tests = 0;
+
+class ACTestSimple: public ACTestBase {
+public:
+ ACTestSimple(const char* banner) : ACTestBase(banner) {}
+ virtual bool Run();
+
+private:
+ void PrintSummary(int total, int fail) {
+ fprintf(stdout, "Test count : %d, fail: %d\n", total, fail);
+ fflush(stdout);
+ }
+};
+}
+
+bool
+ACTestSimple::Run() {
+ int total = 0;
+ int fail = 0;
+
+ vector<TestingCase> *tests = Tests::Get_Tests();
+ if (!tests) {
+ PrintSummary(0, 0);
+ return true;
+ }
+
+ for (vector<TestingCase>::iterator i = tests->begin(), e = tests->end();
+ i != e; i++) {
+ TestingCase& t = *i;
+ int dict_len = t.dict_len;
+ unsigned int* strlen_v = new unsigned int[dict_len];
+
+ fprintf(stdout, ">Testing %s\nDictionary:[ ", t.name);
+ for (int i = 0, need_break=0; i < dict_len; i++) {
+ const char* s = t.dict[i];
+ fprintf(stdout, "%s, ", s);
+ strlen_v[i] = strlen(s);
+ if (need_break++ == 16) {
+ fputs("\n ", stdout);
+ need_break = 0;
+ }
+ }
+ fputs("]\n", stdout);
+
+ /* Create the dictionary */
+ ac_t* ac = ac_create(t.dict, strlen_v, dict_len);
+ delete[] strlen_v;
+
+ for (int ii = 0, ee = t.strpair_num; ii < ee; ii++, total++) {
+ const StrPair& sp = t.strpairs[ii];
+ const char *str = sp.str; // the string to be matched
+ const char *match = sp.match;
+
+ fprintf(stdout, "[%3d] Testing '%s' : ", total, str);
+
+ int len = strlen(str);
+ ac_result_t r;
+ if (t.match_variant == MV_FIRST_MATCH)
+ r = ac_match(ac, str, len);
+ else if (t.match_variant == MV_LEFT_LONGEST)
+ r = ac_match_longest_l(ac, str, len);
+ else {
+ ASSERT(false && "Unknown variant");
+ }
+
+ int m_b = r.match_begin;
+ int m_e = r.match_end;
+
+ // The return value per se is insane.
+ if (m_b > m_e ||
+ ((m_b < 0 || m_e < 0) && (m_b != -1 || m_e != -1))) {
+ fprintf(stdout, "Insane return value (%d, %d)\n", m_b, m_e);
+ fail ++;
+ continue;
+ }
+
+ // If the string is not supposed to match the dictionary.
+ if (!match) {
+ if (m_b != -1 || m_e != -1) {
+ fail ++;
+ fprintf(stdout, "Not Supposed to match (%d, %d) \n",
+ m_b, m_e);
+ } else
+ fputs("Pass\n", stdout);
+ continue;
+ }
+
+ // The string or its substring is match the dict.
+ if (m_b >= len || m_b >= len) {
+ fail ++;
+ fprintf(stdout,
+ "Return value >= the length of the string (%d, %d)\n",
+ m_b, m_e);
+ continue;
+ } else {
+ int mlen = strlen(match);
+ if ((mlen != m_e - m_b + 1) ||
+ strncmp(str + m_b, match, mlen)) {
+ fail ++;
+ fprintf(stdout, "Fail\n");
+ } else
+ fprintf(stdout, "Pass\n");
+ }
+ }
+ fputs("\n", stdout);
+ ac_free(ac);
+ }
+
+ PrintSummary(total, fail);
+ return fail == 0;
+}
+
+bool
+Run_AC_Simple_Test() {
+ ACTestSimple t("AC Simple test");
+ t.PrintBanner();
+ return t.Run();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Testing cases for first-match variant (i.e. test ac_match())
+//
+//////////////////////////////////////////////////////////////////////////////
+//
+
+/* test 1*/
+const char *dict1[] = {"he", "she", "his", "her"};
+StrPair strpair1[] = {
+ {"he", "he"}, {"she", "she"}, {"his", "his"},
+ {"hers", "he"}, {"ahe", "he"}, {"shhe", "he"},
+ {"shis2", "his"}, {"ahhe", "he"}
+};
+Tests test1("test 1",
+ dict1, sizeof(dict1)/sizeof(dict1[0]),
+ strpair1, sizeof(strpair1)/sizeof(strpair1[0]));
+
+/* test 2*/
+const char *dict2[] = {"poto", "poto"}; /* duplicated strings*/
+StrPair strpair2[] = {{"The pot had a handle", 0}};
+Tests test2("test 2", dict2, 2, strpair2, 1);
+
+/* test 3*/
+const char *dict3[] = {"The"};
+StrPair strpair3[] = {{"The pot had a handle", "The"}};
+Tests test3("test 3", dict3, 1, strpair3, 1);
+
+/* test 4*/
+const char *dict4[] = {"pot"};
+StrPair strpair4[] = {{"The pot had a handle", "pot"}};
+Tests test4("test 4", dict4, 1, strpair4, 1);
+
+/* test 5*/
+const char *dict5[] = {"pot "};
+StrPair strpair5[] = {{"The pot had a handle", "pot "}};
+Tests test5("test 5", dict5, 1, strpair5, 1);
+
+/* test 6*/
+const char *dict6[] = {"ot h"};
+StrPair strpair6[] = {{"The pot had a handle", "ot h"}};
+Tests test6("test 6", dict6, 1, strpair6, 1);
+
+/* test 7*/
+const char *dict7[] = {"andle"};
+StrPair strpair7[] = {{"The pot had a handle", "andle"}};
+Tests test7("test 7", dict7, 1, strpair7, 1);
+
+const char *dict8[] = {"aaab"};
+StrPair strpair8[] = {{"aaaaaaab", "aaab"}};
+Tests test8("test 8", dict8, 1, strpair8, 1);
+
+const char *dict9[] = {"haha", "z"};
+StrPair strpair9[] = {{"aaaaz", "z"}, {"z", "z"}};
+Tests test9("test 9", dict9, 2, strpair9, 2);
+
+/* test the case when input string dosen't contain even a single char
+ * of the pattern in dictionary.
+ */
+const char *dict10[] = {"abc"};
+StrPair strpair10[] = {{"cde", 0}};
+Tests test10("test 10", dict10, 1, strpair10, 1);
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Testing cases for first longest match variant (i.e.
+// test ac_match_longest_l())
+//
+//////////////////////////////////////////////////////////////////////////////
+//
+
+// This was actually first motivation for left-longest-match
+const char *dict100[] = {"Mozilla", "Mozilla Mobile"};
+StrPair strpair100[] = {{"User Agent containing string Mozilla Mobile", "Mozilla Mobile"}};
+LeftLongestTests test100("l_test 100", dict100, 2, strpair100, 1);
+
+// Dict with single char is tricky
+const char *dict101[] = {"a", "abc"};
+StrPair strpair101[] = {{"abcdef", "abc"}};
+LeftLongestTests test101("l_test 101", dict101, 2, strpair101, 1);
+
+// Testing case with partially overlapping patterns. The purpose is to
+// check if the fail-link leading from terminal state is correct.
+//
+// The fail-link leading from terminal-state does not matter in
+// match-first-occurrence variant, as it stop when a terminal is hit.
+//
+const char *dict102[] = {"abc", "bcdef"};
+StrPair strpair102[] = {{"abcdef", "bcdef"}};
+LeftLongestTests test102("l_test 102", dict102, 2, strpair102, 1);
diff --git a/modules/policy/lua-aho-corasick/tests/dict/README.txt b/modules/policy/lua-aho-corasick/tests/dict/README.txt
new file mode 100644
index 0000000..cd50b41
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/tests/dict/README.txt
@@ -0,0 +1 @@
+This directory contains pattern set of benchmark purpose.
diff --git a/modules/policy/lua-aho-corasick/tests/dict/dict1.txt b/modules/policy/lua-aho-corasick/tests/dict/dict1.txt
new file mode 100644
index 0000000..94085a9
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/tests/dict/dict1.txt
@@ -0,0 +1,11 @@
+false_return@
+forloop#haha
+wtfprogram
+mmaporunmap
+ThIs?Module!IsEssential
+struct rtlwtf
+gettIMEOfdayWrong
+edistribution_and_use_in_@source
+Copyright~#@
+while {!
+!%SQLinje
diff --git a/modules/policy/lua-aho-corasick/tests/load_ac_test.lua b/modules/policy/lua-aho-corasick/tests/load_ac_test.lua
new file mode 100644
index 0000000..7fb7db9
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/tests/load_ac_test.lua
@@ -0,0 +1,82 @@
+-- This script is to test load_ac.lua
+--
+-- Some notes:
+-- 1. The purpose of this script is not to check if the libac.so work
+-- properly, it is to check if there are something stupid in load_ac.lua
+--
+-- 2. There are bunch of collectgarbage() calls, the purpose is to make
+-- sure the shared lib is not unloaded after GC.
+
+-- load_ac.lua looks up libac.so via package.cpath rather than LD_LIBRARY_PATH,
+-- prepend (instead of appending) some insane paths here to see if it quit
+-- prematurely.
+--
+package.cpath = ".;./?.so;" .. package.cpath
+
+local ac = require "load_ac"
+
+local ac_create = ac.create_ac
+local ac_match = ac.match
+local string_fmt = string.format
+local string_sub = string.sub
+
+local err_cnt = 0
+local function mytest(testname, dict, match, notmatch)
+ print(">Testing ", testname)
+
+ io.write(string_fmt("Dictionary: "));
+ for i=1, #dict do
+ io.write(string_fmt("%s, ", dict[i]))
+ end
+ print ""
+
+ local ac_inst = ac_create(dict);
+ collectgarbage()
+ for i=1, #match do
+ local str = match[i]
+ io.write(string_fmt("Matching %s, ", str))
+ local b = ac_match(ac_inst, str)
+ if b then
+ print "pass"
+ else
+ err_cnt = err_cnt + 1
+ print "fail"
+ end
+ collectgarbage()
+ end
+
+ if notmatch == nil then
+ return
+ end
+
+ collectgarbage()
+
+ for i = 1, #notmatch do
+ local str = notmatch[i]
+ io.write(string_fmt("*Matching %s, ", str))
+ local r = ac_match(ac_inst, str)
+ if r then
+ err_cnt = err_cnt + 1
+ print("fail")
+ else
+ print("succ")
+ end
+ collectgarbage()
+ end
+ ac_inst = nil
+ collectgarbage()
+end
+
+print("")
+print("====== Test to see if load_ac.lua works properly ========")
+
+mytest("test1",
+ {"he", "she", "his", "her", "str\0ing"},
+ -- matching cases
+ { "he", "she", "his", "hers", "ahe", "shhe", "shis2", "ahhe", "str\0ing" },
+
+ -- not matching case
+ {"str\0", "str"}
+ )
+
+os.exit((err_cnt == 0) and 0 or 1)
diff --git a/modules/policy/lua-aho-corasick/tests/lua_test.lua b/modules/policy/lua-aho-corasick/tests/lua_test.lua
new file mode 100644
index 0000000..cfe178f
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/tests/lua_test.lua
@@ -0,0 +1,67 @@
+-- This script is to test ahocorasick.so not libac.so
+--
+local ac = require "ahocorasick"
+
+local ac_create = ac.create
+local ac_match = ac.match
+local string_fmt = string.format
+local string_sub = string.sub
+
+local err_cnt = 0
+local function mytest(testname, dict, match, notmatch)
+ print(">Testing ", testname)
+
+ io.write(string_fmt("Dictionary: "));
+ for i=1, #dict do
+ io.write(string_fmt("%s, ", dict[i]))
+ end
+ print ""
+
+ local ac_inst = ac_create(dict);
+ for i=1, #match do
+ local str = match[i][1]
+ local substr = match[i][2]
+ io.write(string_fmt("Matching %s, ", str))
+ local b, e = ac_match(ac_inst, str)
+ if b and e and (string_sub(str, b+1, e+1) == substr) then
+ print "pass"
+ else
+ err_cnt = err_cnt + 1
+ print "fail"
+ end
+ --print("gc is called")
+ collectgarbage()
+ end
+
+ if notmatch == nil then
+ return
+ end
+
+ for i = 1, #notmatch do
+ local str = notmatch[i]
+ io.write(string_fmt("*Matching %s, ", str))
+ local r = ac_match(ac_inst, str)
+ if r then
+ err_cnt = err_cnt + 1
+ print("fail")
+ else
+ print("succ")
+ end
+ collectgarbage()
+ end
+end
+
+mytest("test1",
+ {"he", "she", "his", "her", "str\0ing"},
+ -- matching cases
+ { {"he", "he"}, {"she", "she"}, {"his", "his"}, {"hers", "he"},
+ {"ahe", "he"}, {"shhe", "he"}, {"shis2", "his"}, {"ahhe", "he"},
+ {"str\0ing", "str\0ing"}
+ },
+
+ -- not matching case
+ {"str\0", "str"}
+
+ )
+
+os.exit((err_cnt == 0) and 0 or 1)
diff --git a/modules/policy/lua-aho-corasick/tests/test_base.hpp b/modules/policy/lua-aho-corasick/tests/test_base.hpp
new file mode 100644
index 0000000..7758371
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/tests/test_base.hpp
@@ -0,0 +1,60 @@
+#ifndef TEST_BASE_H
+#define TEST_BASE_H
+
+#include <stdio.h>
+#include <string>
+#include <stdint.h>
+
+using namespace std;
+class ACTestBase {
+public:
+ ACTestBase(const char* name) :_banner(name) {}
+ virtual void PrintBanner() {
+ fprintf(stdout, "\n===== %s ====\n", _banner.c_str());
+ }
+
+ virtual bool Run() = 0;
+private:
+ string _banner;
+};
+
+typedef std::pair<const char*, int> StrInfo;
+class BigFileTester {
+public:
+ BigFileTester(const char* filepath);
+ virtual ~BigFileTester() { Cleanup(); }
+
+ bool Run();
+
+protected:
+ virtual buf_header_t* PM_Create(const char** strv, uint32_t* strlenv,
+ uint32_t vect_len) = 0;
+ virtual void PM_Free(buf_header_t*) = 0;
+ virtual bool Run_Helper(buf_header_t* PM) = 0;
+
+ // Return true if the '\0' is valid char of a string.
+ virtual bool Str_C_Style() { return true; }
+
+ bool GenerateKeys();
+ void Cleanup();
+ void PrintStr(FILE*, const char* str, int len);
+
+protected:
+ const char* _filepath;
+ int _fd;
+ vector<StrInfo> _keys;
+ char* _msg;
+ int _msg_len;
+ int _key_num; // number of strings in dictionary
+ int _chunk_sz;
+ int _chunk_num;
+
+ int _max_key_num;
+ int _key_min_len;
+ int _key_max_len;
+};
+
+extern bool Run_AC_Simple_Test();
+extern bool Run_AC_Aggressive_Test(const vector<const char*>& files);
+
+#endif
diff --git a/modules/policy/lua-aho-corasick/tests/test_bigfile.cxx b/modules/policy/lua-aho-corasick/tests/test_bigfile.cxx
new file mode 100644
index 0000000..f189d8d
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/tests/test_bigfile.cxx
@@ -0,0 +1,167 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <vector>
+#include <string>
+
+#include "ac.h"
+#include "ac_util.hpp"
+#include "test_base.hpp"
+
+///////////////////////////////////////////////////////////////////////////
+//
+// Implementation of BigFileTester
+//
+///////////////////////////////////////////////////////////////////////////
+//
+BigFileTester::BigFileTester(const char* filepath) {
+ _filepath = filepath;
+ _fd = -1;
+ _msg = (char*)MAP_FAILED;
+ _msg_len = 0;
+ _key_num = 0;
+ _chunk_sz = 0;
+ _chunk_num = 0;
+
+ _max_key_num = 100;
+ _key_min_len = 20;
+ _key_max_len = 80;
+}
+
+void
+BigFileTester::Cleanup() {
+ if (_msg != MAP_FAILED) {
+ munmap((void*)_msg, _msg_len);
+ _msg = (char*)MAP_FAILED;
+ _msg_len = 0;
+ }
+
+ if (_fd != -1) {
+ close(_fd);
+ _fd = -1;
+ }
+}
+
+bool
+BigFileTester::GenerateKeys() {
+ int chunk_sz = 4096;
+ int max_key_num = _max_key_num;
+ int key_min_len = _key_min_len;
+ int key_max_len = _key_max_len;
+
+ int t = _msg_len / chunk_sz;
+ int keynum = t > max_key_num ? max_key_num : t;
+
+ if (keynum <= 4) {
+ // file is too small
+ return false;
+ }
+ chunk_sz = _msg_len / keynum;
+ _chunk_sz = chunk_sz;
+
+ // For each chunck, "randomly" grab a sub-string searving
+ // as key.
+ int random_ofst[] = { 12, 30, 23, 15 };
+ int rofstsz = sizeof(random_ofst)/sizeof(random_ofst[0]);
+ int ofst = 0;
+ const char* msg = _msg;
+ _chunk_num = keynum - 1;
+ for (int idx = 0, e = _chunk_num; idx < e; idx++) {
+ const char* key = msg + ofst + idx % rofstsz;
+ int key_len = key_min_len + idx % (key_max_len - key_min_len);
+ _keys.push_back(StrInfo(key, key_len));
+ ofst += chunk_sz;
+ }
+ return true;
+}
+
+bool
+BigFileTester::Run() {
+ // Step 1: Bring the file into memory
+ fprintf(stdout, "Testing using file '%s'...\n", _filepath);
+
+ int fd = _fd = ::open(_filepath, O_RDONLY);
+ if (fd == -1) {
+ perror("open");
+ return false;
+ }
+
+ struct stat sb;
+ if (fstat(fd, &sb) == -1) {
+ perror("fstat");
+ return false;
+ }
+
+ if (!S_ISREG (sb.st_mode)) {
+ fprintf(stderr, "%s is not regular file\n", _filepath);
+ return false;
+ }
+
+ int ten_M = 1024 * 1024 * 10;
+ int map_sz = _msg_len = sb.st_size > ten_M ? ten_M : sb.st_size;
+ char* p = _msg =
+ (char*)mmap (0, map_sz, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
+ if (p == MAP_FAILED) {
+ perror("mmap");
+ return false;
+ }
+
+ // Get rid of '\0' if we are picky at it.
+ if (Str_C_Style()) {
+ for (int i = 0; i < map_sz; i++) { if (!p[i]) p[i] = 'a'; }
+ p[map_sz - 1] = 0;
+ }
+
+ // Step 2: "Fabricate" some keys from the file.
+ if (!GenerateKeys()) {
+ close(fd);
+ return false;
+ }
+
+ // Step 3: Create PM instance
+ const char** keys = new const char*[_keys.size()];
+ unsigned int* keylens = new unsigned int[_keys.size()];
+
+ int i = 0;
+ for (vector<StrInfo>::iterator si = _keys.begin(), se = _keys.end();
+ si != se; si++, i++) {
+ const StrInfo& strinfo = *si;
+ keys[i] = strinfo.first;
+ keylens[i] = strinfo.second;
+ }
+
+ buf_header_t* PM = PM_Create(keys, keylens, i);
+ delete[] keys;
+ delete[] keylens;
+
+ // Step 4: Run testing
+ bool res = Run_Helper(PM);
+ PM_Free(PM);
+
+ // Step 5: Clanup
+ munmap(p, map_sz);
+ _msg = (char*)MAP_FAILED;
+ close(fd);
+ _fd = -1;
+
+ fprintf(stdout, "%s\n", res ? "succ" : "fail");
+ return res;
+}
+
+void
+BigFileTester::PrintStr(FILE* f, const char* str, int len) {
+ fprintf(f, "{");
+ for (int i = 0; i < len; i++) {
+ unsigned char c = str[i];
+ if (isprint(c))
+ fprintf(f, "'%c', ", c);
+ else
+ fprintf(f, "%#x, ", c);
+ }
+ fprintf(f, "}");
+};
diff --git a/modules/policy/lua-aho-corasick/tests/test_main.cxx b/modules/policy/lua-aho-corasick/tests/test_main.cxx
new file mode 100644
index 0000000..b4f5225
--- /dev/null
+++ b/modules/policy/lua-aho-corasick/tests/test_main.cxx
@@ -0,0 +1,33 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <vector>
+#include <string>
+#include "ac.h"
+#include "ac_util.hpp"
+#include "test_base.hpp"
+
+using namespace std;
+
+
+/////////////////////////////////////////////////////////////////////////
+//
+// Simple (yet maybe tricky) testings
+//
+/////////////////////////////////////////////////////////////////////////
+//
+int
+main (int argc, char** argv) {
+ bool succ = Run_AC_Simple_Test();
+
+ vector<const char*> files;
+ for (int i = 1; i < argc; i++) { files.push_back(argv[i]); }
+ succ = Run_AC_Aggressive_Test(files) && succ;
+
+ return succ ? 0 : -1;
+};
diff --git a/modules/policy/meson.build b/modules/policy/meson.build
new file mode 100644
index 0000000..37f1683
--- /dev/null
+++ b/modules/policy/meson.build
@@ -0,0 +1,50 @@
+# LUA module: policy
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+lua_mod_src += [
+ files('policy.lua'),
+]
+
+config_tests += [
+ ['policy', files('policy.test.lua')],
+ ['policy.slice', files('policy.slice.test.lua')],
+ ['policy.rpz', files('policy.rpz.test.lua')],
+]
+
+integr_tests += [
+ ['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
+lua_ac_submodule = run_command(['test', '-r',
+ '@0@/lua-aho-corasick/ac_fast.cxx'.format(meson.current_source_dir())],
+ check: false)
+if lua_ac_submodule.returncode() != 0
+ error('run "git submodule update --init --recursive" to initialize git submodules')
+endif
+
+# compile bundled lua-aho-corasick as shared module
+lua_ac_src = files([
+ 'lua-aho-corasick/ac_fast.cxx',
+ 'lua-aho-corasick/ac_lua.cxx',
+ 'lua-aho-corasick/ac_slow.cxx',
+])
+
+lua_ac_lib = shared_module(
+ 'ahocorasick',
+ lua_ac_src,
+ cpp_args: [
+ '-fvisibility=hidden',
+ '-Wall',
+ '-fPIC',
+ ],
+ dependencies: [
+ luajit_inc,
+ ],
+ include_directories: mod_inc_dir,
+ name_prefix: '',
+ install: true,
+ install_dir: lib_dir,
+)
diff --git a/modules/policy/noipv6.test.integr/broken-ipv6.rpl b/modules/policy/noipv6.test.integr/broken-ipv6.rpl
new file mode 100644
index 0000000..cb0738d
--- /dev/null
+++ b/modules/policy/noipv6.test.integr/broken-ipv6.rpl
@@ -0,0 +1,47 @@
+; config options
+; SPDX-License-Identifier: GPL-3.0-or-later
+ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test that IPv6 is not used by kresd.
+
+RANGE_BEGIN 0 100
+ ADDRESS ::1:2:3:4
+RANGE_END
+
+RANGE_BEGIN 0 100
+ ADDRESS 1.2.3.4
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.test.org A
+SECTION ANSWER
+www.test.org 3600 A 4.3.2.1
+ENTRY_END
+
+RANGE_END
+
+
+STEP 10 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+www.test.org A
+ENTRY_END
+
+STEP 20 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.test.org A
+SECTION ANSWER
+www.test.org 3600 A 4.3.2.1
+SECTION AUTHORITY
+SECTION ADDITIONAL
+ENTRY_END
+
+SCENARIO_END
diff --git a/modules/policy/noipv6.test.integr/deckard.yaml b/modules/policy/noipv6.test.integr/deckard.yaml
new file mode 100644
index 0000000..4c1b6f8
--- /dev/null
+++ b/modules/policy/noipv6.test.integr/deckard.yaml
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+programs:
+- name: kresd
+ binary: kresd
+ additional:
+ - --noninteractive
+ templates:
+ - modules/policy/noipv6.test.integr/kresd_config.j2
+ - tests/integration/hints_zone.j2
+ configs:
+ - config
+ - hints
diff --git a/modules/policy/noipv6.test.integr/kresd_config.j2 b/modules/policy/noipv6.test.integr/kresd_config.j2
new file mode 100644
index 0000000..a897d17
--- /dev/null
+++ b/modules/policy/noipv6.test.integr/kresd_config.j2
@@ -0,0 +1,59 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+{% raw %}
+net.ipv6 = false
+policy.add(policy.all(policy.STUB({ '::1:2:3:4', '1.2.3.4' })))
+
+-- make sure DNSSEC is turned off for tests
+trust_anchors.remove('.')
+
+-- 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
+
+_hint_root_file('hints')
+cache.size = 2*MB
+log_level('debug')
+{% endraw %}
+
+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(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 --git a/modules/policy/noipvx.test.integr/broken-ipvx.rpl b/modules/policy/noipvx.test.integr/broken-ipvx.rpl
new file mode 100644
index 0000000..60ed618
--- /dev/null
+++ b/modules/policy/noipvx.test.integr/broken-ipvx.rpl
@@ -0,0 +1,35 @@
+; config options
+; SPDX-License-Identifier: GPL-3.0-or-later
+ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test that neither IPv6 nor IPv4 is used by kresd :-)
+
+RANGE_BEGIN 0 100
+ ADDRESS ::1:2:3:4
+RANGE_END
+
+RANGE_BEGIN 0 100
+ ADDRESS 1.2.3.4
+RANGE_END
+
+
+STEP 10 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+www.test.org A
+ENTRY_END
+
+STEP 20 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer
+REPLY QR RD RA SERVFAIL
+SECTION QUESTION
+www.test.org A
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+ENTRY_END
+
+SCENARIO_END
diff --git a/modules/policy/noipvx.test.integr/deckard.yaml b/modules/policy/noipvx.test.integr/deckard.yaml
new file mode 100644
index 0000000..8178759
--- /dev/null
+++ b/modules/policy/noipvx.test.integr/deckard.yaml
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+programs:
+- name: kresd
+ binary: kresd
+ additional:
+ - --noninteractive
+ templates:
+ - modules/policy/noipvx.test.integr/kresd_config.j2
+ - tests/integration/hints_zone.j2
+ configs:
+ - config
+ - hints
diff --git a/modules/policy/noipvx.test.integr/kresd_config.j2 b/modules/policy/noipvx.test.integr/kresd_config.j2
new file mode 100644
index 0000000..87873e8
--- /dev/null
+++ b/modules/policy/noipvx.test.integr/kresd_config.j2
@@ -0,0 +1,60 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+{% raw %}
+net.ipv4 = false
+net.ipv6 = false
+policy.add(policy.all(policy.STUB({ '::1:2:3:4', '1.2.3.4' })))
+
+-- make sure DNSSEC is turned off for tests
+trust_anchors.remove('.')
+
+-- 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
+
+_hint_root_file('hints')
+cache.size = 2*MB
+log_level('debug')
+{% endraw %}
+
+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(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 --git a/modules/policy/policy.lua b/modules/policy/policy.lua
new file mode 100644
index 0000000..47e436f
--- /dev/null
+++ b/modules/policy/policy.lua
@@ -0,0 +1,1109 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local kres = require('kres')
+local ffi = require('ffi')
+
+local LOG_GRP_POLICY_TAG = ffi.string(ffi.C.kr_log_grp2name(ffi.C.LOG_GRP_POLICY))
+local LOG_GRP_REQDBG_TAG = ffi.string(ffi.C.kr_log_grp2name(ffi.C.LOG_GRP_REQDBG))
+
+local todname = kres.str2dname -- not available during module load otherwise
+
+-- Counter of unique rules
+local nextid = 0
+local function getruleid()
+ local newid = nextid
+ nextid = nextid + 1
+ return newid
+end
+
+-- Support for client sockets from inside policy actions
+local socket_client = function ()
+ return error("missing lua-cqueues library, can't create socket client")
+end
+local has_socket, socket = pcall(require, 'cqueues.socket')
+if has_socket then
+ socket_client = function (host, port)
+ local s, err, status
+
+ s = socket.connect({ host = host, port = port, type = socket.SOCK_DGRAM })
+ s:setmode('bn', 'bn')
+ status, err = pcall(s.connect, s)
+
+ if not status then
+ return status, err
+ end
+ return s
+ end
+end
+
+-- Split address and port from a combined string.
+local function addr_split_port(target, default_port)
+ assert(default_port and type(default_port) == 'number')
+ local port = ffi.new('uint16_t[1]', default_port)
+ local addr = ffi.new('char[47]') -- INET6_ADDRSTRLEN + 1
+ local ret = ffi.C.kr_straddr_split(target, addr, port)
+ if ret ~= 0 then
+ error('failed to parse address ' .. target)
+ end
+ return addr, tonumber(port[0])
+end
+
+-- String address@port -> sockaddr.
+local function addr2sock(target, default_port)
+ local addr, port = addr_split_port(target, default_port)
+ local sock = ffi.gc(ffi.C.kr_straddr_socket(addr, port, nil), ffi.C.free);
+ if sock == nil then
+ error("target '"..target..'" is not a valid IP address')
+ end
+ return sock
+end
+
+-- Debug logging for taken policy actions
+local function log_policy_action(req, name)
+ if ffi.C.kr_log_is_debug_fun(ffi.C.LOG_GRP_POLICY, req) then
+ local qry = req:current()
+ ffi.C.kr_log_req1(
+ req, qry.uid, 2, ffi.C.LOG_GRP_POLICY, LOG_GRP_POLICY_TAG,
+ "%s applied for %s %s\n",
+ name, kres.dname2str(qry.sname), kres.tostring.type[qry.stype])
+ end
+end
+
+-- policy functions are defined below
+local policy = {}
+
+function policy.PASS(state, _)
+ return state
+end
+
+-- Mirror request elsewhere, and continue solving
+function policy.MIRROR(target)
+ local addr, port = addr_split_port(target, 53)
+ local sink, err = socket_client(ffi.string(addr), port)
+ if not sink then panic('MIRROR target %s is not a valid: %s', target, err) end
+ return function(state, req)
+ if state == kres.FAIL then return state end
+ local query = req.qsource.packet
+ if query ~= nil then
+ sink:send(ffi.string(query.wire, query.size), 1, tonumber(query.size))
+ end
+ return -- Chain action to next
+ end
+end
+
+-- Override the list of nameservers (forwarders)
+local function set_nslist(req, list)
+ local ns_i = 0
+ for _, ns in ipairs(list) do
+ if ffi.C.kr_forward_add_target(req, ns) == 0 then
+ ns_i = ns_i + 1
+ end
+ end
+ if ns_i == 0 then
+ -- would use assert() but don't want to compose the message if not triggered
+ error('no usable address in NS set (check net.ipv4 and '
+ .. 'net.ipv6 config):\n' .. table_print(list, 2))
+ end
+end
+
+-- Forward request, and solve as stub query
+function policy.STUB(target)
+ local list = {}
+ if type(target) == 'table' then
+ for _, v in pairs(target) do
+ table.insert(list, addr2sock(v, 53))
+ end
+ else
+ table.insert(list, addr2sock(target, 53))
+ end
+ return function(state, req)
+ local qry = req:current()
+ -- Switch mode to stub resolver, do not track origin zone cut since it's not real authority NS
+ qry.flags.STUB = true
+ qry.flags.ALWAYS_CUT = false
+ set_nslist(req, list)
+ return state
+ end
+end
+
+-- Forward request and all subrequests to upstream; validate answers
+function policy.FORWARD(target)
+ local list = {}
+ if type(target) == 'table' then
+ for _, v in pairs(target) do
+ table.insert(list, addr2sock(v, 53))
+ end
+ else
+ table.insert(list, addr2sock(target, 53))
+ end
+ return function(state, req)
+ local qry = req:current()
+ req.options.FORWARD = true
+ req.options.NO_MINIMIZE = true
+ qry.flags.FORWARD = true
+ qry.flags.ALWAYS_CUT = false
+ qry.flags.NO_MINIMIZE = true
+ qry.flags.AWAIT_CUT = true
+ set_nslist(req, list)
+ return state
+ end
+end
+
+-- Forward request and all subrequests to upstream over TLS; validate answers
+function policy.TLS_FORWARD(targets)
+ if type(targets) ~= 'table' or #targets < 1 then
+ error('TLS_FORWARD argument must be a non-empty table')
+ end
+
+ local sockaddr_c_set = {}
+ local nslist = {} -- to persist in closure of the returned function
+ for idx, target in pairs(targets) do
+ if type(target) ~= 'table' or type(target[1]) ~= 'string' then
+ error(string.format('TLS_FORWARD configuration at position ' ..
+ '%d must be a table starting with an IP address', idx))
+ end
+ -- Note: some functions have checks with error() calls inside.
+ local sockaddr_c = addr2sock(target[1], 853)
+
+ -- Refuse repeated addresses in the same set.
+ local sockaddr_lua = ffi.string(sockaddr_c, ffi.C.kr_sockaddr_len(sockaddr_c))
+ if sockaddr_c_set[sockaddr_lua] then
+ error('TLS_FORWARD configuration cannot declare two configs for IP address '
+ .. target[1])
+ else
+ sockaddr_c_set[sockaddr_lua] = true;
+ end
+
+ table.insert(nslist, sockaddr_c)
+ net.tls_client(target)
+ end
+
+ return function(state, req)
+ local qry = req:current()
+ req.options.FORWARD = true
+ req.options.NO_MINIMIZE = true
+ qry.flags.FORWARD = true
+ qry.flags.ALWAYS_CUT = false
+ qry.flags.NO_MINIMIZE = true
+ qry.flags.AWAIT_CUT = true
+ req.options.TCP = true
+ qry.flags.TCP = true
+ set_nslist(req, nslist)
+ return state
+ end
+end
+
+-- Rewrite records in packet
+function policy.REROUTE(tbl, names)
+ -- Import renumbering rules
+ local ren = require('kres_modules.renumber')
+ local prefixes = {}
+ for from, to in pairs(tbl) do
+ local prefix = names and ren.name(from, to) or ren.prefix(from, to)
+ table.insert(prefixes, prefix)
+ end
+ -- Return rule closure
+ return ren.rule(prefixes)
+end
+
+-- Set and clear some query flags
+function policy.FLAGS(opts_set, opts_clear)
+ return function(_, req)
+ -- We assume to be running in the begin phase, so to truly apply
+ -- to the whole request we need to change both kr_request and kr_query.
+ local qry = req:current()
+ for _, flags in pairs({qry.flags, req.options}) do
+ ffi.C.kr_qflags_set (flags, kres.mk_qflags(opts_set or {}))
+ ffi.C.kr_qflags_clear(flags, kres.mk_qflags(opts_clear or {}))
+ end
+ return nil -- chain rule
+ end
+end
+
+local function mkauth_soa(answer, dname, mname, ttl)
+ if mname == nil then
+ mname = dname
+ end
+ return answer:put(dname, ttl or 10800, answer:qclass(), kres.type.SOA,
+ mname .. '\6nobody\7invalid\0\0\0\0\1\0\0\14\16\0\0\4\176\0\9\58\128\0\0\42\48')
+end
+
+-- Create answer with passed arguments
+function policy.ANSWER(rtable, nodata)
+ return function(_, req)
+ local qry = req:current()
+ local data = rtable[qry.stype]
+ if data == nil and nodata ~= true then
+ return nil
+ end
+ -- now we're certain we want to generate an answer
+
+ local answer = req:ensure_answer()
+ if answer == nil then return nil end
+ ffi.C.kr_pkt_make_auth_header(answer)
+ local ttl = (data or {}).ttl or 1
+ answer:rcode(kres.rcode.NOERROR)
+ req:set_extended_error(kres.extended_error.FORGED, "5DO5")
+
+ if data == nil then -- want NODATA, i.e. just a SOA
+ answer:begin(kres.section.AUTHORITY)
+ local soa = rtable[kres.type.SOA]
+ if soa ~= nil then
+ answer:put(qry.sname, soa.ttl or ttl, qry.sclass, kres.type.SOA,
+ soa.rdata[1] or soa.rdata)
+ else
+ mkauth_soa(answer, kres.dname2wire(qry.sname), nil, ttl)
+ end
+ log_policy_action(req, 'ANSWER (nodata)')
+ else
+ answer:begin(kres.section.ANSWER)
+ if type(data.rdata) == 'table' then
+ for _, entry in ipairs(data.rdata) do
+ answer:put(qry.sname, ttl, qry.sclass, qry.stype, entry)
+ end
+ else
+ answer:put(qry.sname, ttl, qry.sclass, qry.stype, data.rdata)
+ end
+ log_policy_action(req, 'ANSWER (forged)')
+ end
+ return kres.DONE
+ end
+end
+
+local dname_localhost = todname('localhost.')
+
+-- Rule for localhost. zone; see RFC6303, sec. 3
+local function localhost(_, req)
+ local qry = req:current()
+ 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)
+
+ answer:rcode(kres.rcode.NOERROR)
+ answer:begin(kres.section.ANSWER)
+ if qry.stype == kres.type.AAAA then
+ answer:put(qry.sname, 900, answer:qclass(), kres.type.AAAA,
+ '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1')
+ elseif qry.stype == kres.type.A then
+ answer:put(qry.sname, 900, answer:qclass(), kres.type.A, '\127\0\0\1')
+ elseif is_exact and qry.stype == kres.type.SOA then
+ mkauth_soa(answer, dname_localhost)
+ elseif is_exact and qry.stype == kres.type.NS then
+ answer:put(dname_localhost, 900, answer:qclass(), kres.type.NS, dname_localhost)
+ else
+ answer:begin(kres.section.AUTHORITY)
+ mkauth_soa(answer, dname_localhost)
+ end
+ return kres.DONE
+end
+
+local dname_rev4_localhost = todname('1.0.0.127.in-addr.arpa');
+local dname_rev4_localhost_apex = todname('127.in-addr.arpa');
+
+-- Rule for reverse localhost.
+-- Answer with locally served minimal 127.in-addr.arpa domain, only having
+-- a PTR record in 1.0.0.127.in-addr.arpa, and with 1.0...0.ip6.arpa. zone.
+-- 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:ensure_answer()
+ if answer == nil then return nil end
+
+ -- classify qry.sname:
+ local is_exact -- exact dname for localhost
+ local is_apex -- apex of a locally-served localhost zone
+ local is_nonterm -- empty non-terminal name
+ if ffi.C.knot_dname_in_bailiwick(qry.sname, todname('ip6.arpa.')) > 0 then
+ -- exact ::1 query (relying on the calling rule)
+ is_exact = true
+ is_apex = true
+ else
+ -- within 127.in-addr.arpa.
+ local labels = ffi.C.knot_dname_labels(qry.sname, nil)
+ if labels == 3 then
+ is_exact = false
+ is_apex = true
+ elseif labels == 4+2 and ffi.C.knot_dname_is_equal(
+ qry.sname, dname_rev4_localhost) then
+ is_exact = true
+ else
+ is_exact = false
+ is_apex = false
+ is_nonterm = ffi.C.knot_dname_in_bailiwick(dname_rev4_localhost, qry.sname) > 0
+ end
+ end
+
+ ffi.C.kr_pkt_make_auth_header(answer)
+ answer:rcode(kres.rcode.NOERROR)
+ answer:begin(kres.section.ANSWER)
+ if is_exact and qry.stype == kres.type.PTR then
+ answer:put(qry.sname, 900, answer:qclass(), kres.type.PTR, dname_localhost)
+ elseif is_apex and qry.stype == kres.type.SOA then
+ mkauth_soa(answer, dname_rev4_localhost_apex, dname_localhost)
+ elseif is_apex and qry.stype == kres.type.NS then
+ answer:put(dname_rev4_localhost_apex, 900, answer:qclass(), kres.type.NS,
+ dname_localhost)
+ else
+ if not is_nonterm then
+ answer:rcode(kres.rcode.NXDOMAIN)
+ end
+ answer:begin(kres.section.AUTHORITY)
+ mkauth_soa(answer, dname_rev4_localhost_apex, dname_localhost)
+ end
+ return kres.DONE
+end
+
+-- All requests
+function policy.all(action)
+ return function(_, _) return action end
+end
+
+-- Requests whose QNAME is exactly the provided domain
+function policy.domains(action, dname_list)
+ return function(_, query)
+ local qname = query:name()
+ for _, dname in ipairs(dname_list) do
+ if ffi.C.knot_dname_is_equal(qname, dname) then
+ return action
+ end
+ end
+ return nil
+ end
+end
+
+-- Requests whose QNAME matches given zone list (i.e. suffix match)
+function policy.suffix(action, zone_list)
+ local AC = require('ahocorasick')
+ local tree = AC.create(zone_list)
+ return function(_, query)
+ local match = AC.match(tree, query:name(), false)
+ if match ~= nil then
+ return action
+ end
+ return nil
+ end
+end
+
+-- Check for common suffix first, then suffix match (specialized version of suffix match)
+function policy.suffix_common(action, suffix_list, common_suffix)
+ local common_len = string.len(common_suffix)
+ local suffix_count = #suffix_list
+ return function(_, query)
+ -- Preliminary check
+ local qname = query:name()
+ if not string.find(qname, common_suffix, -common_len, true) then
+ return nil
+ end
+ -- String match
+ for i = 1, suffix_count do
+ local zone = suffix_list[i]
+ if string.find(qname, zone, -string.len(zone), true) then
+ return action
+ end
+ end
+ return nil
+ end
+end
+
+-- Filter QNAME pattern
+function policy.pattern(action, pattern)
+ return function(_, query)
+ if string.find(query:name(), pattern) then
+ return action
+ end
+ return nil
+ end
+end
+
+local function rpz_parse(action, path)
+ local rules = {}
+ local new_actions = {}
+ local action_map = {
+ -- RPZ Policy Actions
+ ['\0'] = action,
+ ['\1*\0'] = policy.ANSWER({}, true),
+ ['\012rpz-passthru\0'] = policy.PASS, -- the grammar...
+ ['\008rpz-drop\0'] = policy.DROP,
+ ['\012rpz-tcp-only\0'] = policy.TC,
+ -- Policy triggers @NYI@
+ }
+ -- 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.DNSKEY] = true,
+ [kres.type.DS] = true,
+ [kres.type.RRSIG] = true,
+ [kres.type.NSEC] = true,
+ [kres.type.NSEC3] = true,
+ }
+
+ -- We generally don't know what zone should be in the file; we try to detect it.
+ -- Fortunately, it's typical that SOA is the first record, even required for AXFR.
+ local origin_soa = nil
+ local warned_soa, warned_bailiwick
+
+ local parser = require('zonefile').new()
+ local ok, errstr = parser:open(path)
+ if not ok then
+ error(string.format('failed to parse "%s": %s', path, errstr or "unknown error"))
+ end
+ while true do
+ ok, errstr = parser:parse()
+ if errstr then
+ log_warn(ffi.C.LOG_GRP_POLICY, 'RPZ %s:%d: %s',
+ path, tonumber(parser.line_counter), errstr)
+ end
+ if not ok then break end
+
+ local full_name = ffi.gc(ffi.C.knot_dname_copy(parser.r_owner, nil), ffi.C.free)
+ local rdata = ffi.string(parser.r_data, parser.r_data_length)
+ ffi.C.knot_dname_to_lower(full_name)
+
+ local origin = origin_soa or parser.zone_origin
+ local prefix_labels = ffi.C.knot_dname_in_bailiwick(full_name, origin)
+ if prefix_labels < 0 then
+ if not warned_bailiwick then
+ warned_bailiwick = true
+ log_warn(ffi.C.LOG_GRP_POLICY,
+ 'RPZ %s:%d: RR owner "%s" outside the zone (ignored; reported once per file)',
+ path, tonumber(parser.line_counter), kres.dname2str(full_name))
+ end
+ goto continue
+ end
+
+ local bytes = ffi.C.knot_dname_size(full_name) - ffi.C.knot_dname_size(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_warn(ffi.C.LOG_GRP_POLICY,
+ 'RPZ %s:%d: CNAME with custom target in RPZ is not supported yet (ignored)',
+ path, tonumber(parser.line_counter))
+ end
+ else
+ if #name then
+ local is_bad = rrtype_bad[parser.r_type]
+
+ if parser.r_type == kres.type.SOA then
+ if origin_soa == nil then
+ origin_soa = ffi.gc(ffi.C.knot_dname_copy(parser.r_owner, nil), ffi.C.free)
+ goto continue -- we don't want to modify `new_actions`
+ else
+ is_bad = true -- maybe provide more info, but it seems rare
+ end
+ elseif origin_soa == nil and not warned_soa then
+ warned_soa = true
+ log_warn(ffi.C.LOG_GRP_POLICY,
+ 'RPZ %s:%d warning: SOA missing as the first record',
+ path, tonumber(parser.line_counter))
+ end
+
+ if is_bad == true or (is_bad == false and prefix_labels ~= 0) then
+ log_warn(ffi.C.LOG_GRP_POLICY, '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
+ 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 -- multiple 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_warn(ffi.C.LOG_GRP_POLICY, '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
+
+ ::continue::
+ end
+ collectgarbage()
+ for qname, rrsets in pairs(new_actions) do
+ rules[qname] = policy.ANSWER(rrsets, true)
+ end
+ return rules
+end
+
+-- Split path into dirname and basename (like the shell utilities)
+local function get_dir_and_file(path)
+ local dir, file = string.match(path, "(.*)/([^/]+)")
+
+ -- If regex doesn't match then path must be the file directly (i.e. doesn't contain '/')
+ -- This assumes that the file exists (rpz_parse() would fail if it doesn't)
+ if not dir and not file then
+ dir = '.'
+ file = path
+ end
+
+ return dir, file
+end
+
+-- RPZ policy set
+-- Create RPZ from zone file and optionally watch the file for changes
+function policy.rpz(action, path, watch)
+ local rules = rpz_parse(action, path)
+
+ if watch ~= false then
+ local has_notify, notify = pcall(require, 'cqueues.notify')
+ if has_notify then
+ local bit = require('bit')
+
+ local dir, file = get_dir_and_file(path)
+ local watcher = notify.opendir(dir)
+ watcher:add(file, bit.bxor(notify.CREATE, notify.MODIFY))
+
+ worker.coroutine(function ()
+ for _, name in watcher:changes() do
+ -- Limit to changes on file we're interested in
+ -- Watcher will also fire for changes to the directory itself
+ if name == file then
+ -- If the file changes then reparse and replace the existing ruleset
+ log_info(ffi.C.LOG_GRP_POLICY, 'RPZ reloading: ' .. name)
+ rules = rpz_parse(action, path)
+ end
+ end
+ end)
+ elseif watch then -- explicitly requested and failed
+ error('[poli] lua-cqueues required to watch and reload RPZ file')
+ else
+ log_info(ffi.C.LOG_GRP_POLICY, 'lua-cqueues required to watch and reload RPZ file, continuing without watching')
+ end
+ end
+
+ return function(_, query)
+ local label = query:name()
+ local rule = rules[label]
+ while rule == nil and string.len(label) > 0 do
+ label = string.sub(label, string.byte(label) + 2)
+ rule = rules['\1*'..label]
+ end
+ return rule
+ end
+end
+
+-- Apply an action when query belongs to a slice (determined by slice_func())
+function policy.slice(slice_func, ...)
+ local actions = {...}
+ if #actions <= 0 then
+ error('[poli] at least one action must be provided to policy.slice()')
+ end
+
+ return function(_, query)
+ local index = slice_func(query, #actions)
+ return actions[index]
+ end
+end
+
+-- Initializes slicing function that randomly assigns queries to a slice based on their registrable domain
+function policy.slice_randomize_psl(seed)
+ local has_psl, psl_lib = pcall(require, 'psl')
+ if not has_psl then
+ error('[poli] lua-psl is required for policy.slice_randomize_psl()')
+ end
+ -- load psl
+ local has_latest, psl = pcall(psl_lib.latest)
+ if not has_latest then -- compatibility with lua-psl < 0.15
+ psl = psl_lib.builtin()
+ end
+
+ if seed == nil then
+ seed = os.time() / (3600 * 24 * 7)
+ end
+ seed = math.floor(seed) -- convert to int
+
+ return function(query, length)
+ assert(length > 0)
+
+ local domain = kres.dname2str(query:name())
+ if domain == nil then -- invalid data: auto-select first action
+ return 1
+ end
+ if domain:len() > 1 then --remove trailing dot
+ domain = domain:sub(0, -2)
+ end
+
+ -- do psl lookup for registrable domain
+ local reg_domain = psl:registrable_domain(domain)
+ if reg_domain == nil then -- fallback to unreg. domain
+ reg_domain = psl:unregistrable_domain(domain)
+ if reg_domain == nil then -- shouldn't happen: safe fallback
+ return 1
+ end
+ end
+
+ local rand_seed = seed
+ -- create deterministic seed for pseudo-random slice assignment
+ for i = 1, #reg_domain do
+ rand_seed = rand_seed + reg_domain:byte(i)
+ end
+
+ -- use linear congruential generator with values from ANSI C
+ rand_seed = rand_seed % 0x80000000 -- ensure seed is positive 32b int
+ local rand = (1103515245 * rand_seed + 12345) % 0x10000
+ return 1 + rand % length
+ end
+end
+
+-- Prepare for making an answer from scratch. (Return the packet for convenience.)
+local function answer_clear(req)
+ -- If we're in postrules, previous resolving might have chosen some RRs
+ -- for inclusion in the answer, so we need to avoid those.
+ -- *_selected arrays are in mempool, so explicit deallocation is not necessary.
+ req.answ_selected.len = 0
+ req.auth_selected.len = 0
+ req.add_selected.len = 0
+
+ -- Let's be defensive and clear the answer, too.
+ local pkt = req:ensure_answer()
+ if pkt == nil then return nil end
+ pkt:clear_payload()
+ req:ensure_edns()
+ return pkt
+end
+
+function policy.DENY_MSG(msg, extended_error)
+ if msg and (type(msg) ~= 'string' or #msg >= 255) then
+ error('DENY_MSG: optional msg must be string shorter than 256 characters')
+ end
+ if extended_error == nil then
+ extended_error = kres.extended_error.BLOCKED
+ end
+ local action_name = msg and 'DENY_MSG' or 'DENY'
+
+ 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)
+ mkauth_soa(answer, answer:qname())
+ if msg then
+ answer:begin(kres.section.ADDITIONAL)
+ answer:put('\11explanation\7invalid', 10800, answer:qclass(), kres.type.TXT,
+ string.char(#msg) .. msg)
+
+ end
+ req:set_extended_error(extended_error, "CR36")
+ log_policy_action(req, action_name)
+ return kres.DONE
+ end
+end
+
+local function free_cb(func)
+ func:free()
+end
+
+local debug_logline_cb = ffi.cast('trace_log_f', function (_, msg)
+ jit.off(true, true) -- JIT for (C -> lua)^2 nesting isn't allowed
+ ffi.C.kr_log_fmt(
+ ffi.C.LOG_GRP_REQDBG, -- but the original [group] tag also remains in the string
+ LOG_DEBUG,
+ 'CODE_FILE=policy.lua', 'CODE_LINE=', 'CODE_FUNC=policy.DEBUG_ALWAYS', -- no meaningful locations
+ '[%-6s]%s', LOG_GRP_REQDBG_TAG, msg) -- msg should end with newline already
+end)
+ffi.gc(debug_logline_cb, free_cb)
+
+-- LOG_DEBUG without log_trace and without code locations
+local function log_notrace(req, fmt, ...)
+ ffi.C.kr_log_fmt(ffi.C.LOG_GRP_REQDBG, LOG_DEBUG,
+ 'CODE_FILE=policy.lua', 'CODE_LINE=', 'CODE_FUNC=', -- no meaningful locations
+ '%s', string.format( -- convert in lua, as integers in C varargs would pass as double
+ '[%-6s][%-6s][%05u.00] ' .. fmt,
+ LOG_GRP_REQDBG_TAG, LOG_GRP_POLICY_TAG, req.uid, ...)
+ )
+end
+
+local debug_logfinish_cb = ffi.cast('trace_callback_f', function (req)
+ jit.off(true, true) -- JIT for (C -> lua)^2 nesting isn't allowed
+ log_notrace(req, 'following rrsets were marked as interesting:\n%s\n',
+ req:selected_tostring())
+ if req.answer ~= nil then
+ log_notrace(req, 'answer packet:\n%s\n', req.answer)
+ else
+ log_notrace(req, 'answer packet DROPPED\n')
+ end
+end)
+ffi.gc(debug_logfinish_cb, free_cb)
+
+-- log request packet
+function policy.REQTRACE(_, req)
+ log_notrace(req, 'request packet:\n%s', req.qsource.packet)
+end
+
+-- log how the request arrived, notably the client's IP
+function policy.IPTRACE(_, req)
+ if req.qsource.addr == nil then
+ log_notrace(req, 'request packet arrived internally\n')
+ else
+ -- stringify transport flags: struct kr_request_qsource_flags
+ local qf = req.qsource.flags
+ local qf_str = qf.tcp and 'TCP' or 'UDP'
+ if qf.tls then qf_str = qf_str .. ' + TLS' end
+ if qf.http then qf_str = qf_str .. ' + HTTP' end
+ if qf.xdp then qf_str = qf_str .. ' + XDP' end
+
+ log_notrace(req, 'request packet arrived from %s to %s (%s)\n',
+ req.qsource.addr, req.qsource.dst_addr, qf_str)
+ end
+ return nil -- chain rule
+end
+
+function policy.DEBUG_ALWAYS(state, req)
+ policy.QTRACE(state, req)
+ req:trace_chain_callbacks(debug_logline_cb, debug_logfinish_cb)
+ policy.REQTRACE(state, req)
+end
+
+local debug_stashlog_cb = ffi.cast('trace_log_f', function (req, msg)
+ jit.off(true, true) -- JIT for (C -> lua)^2 nesting isn't allowed
+
+ -- stash messages for conditional logging in trace_finish
+ local stash = req:vars()['policy_debug_stash']
+ table.insert(stash, ffi.string(msg))
+end)
+ffi.gc(debug_stashlog_cb, free_cb)
+
+-- buffer debug logs and print then only if test() returns a truthy value
+function policy.DEBUG_IF(test)
+ local debug_finish_cb = ffi.cast('trace_callback_f', function (cbreq)
+ jit.off(true, true) -- JIT for (C -> lua)^2 nesting isn't allowed
+ if test(cbreq) then
+ policy.REQTRACE(nil, cbreq)
+ debug_logfinish_cb(cbreq) -- unconditional version
+
+ local stash = cbreq:vars()['policy_debug_stash']
+ for _, line in ipairs(stash) do -- don't want one huge entry
+ ffi.C.kr_log_fmt(ffi.C.LOG_GRP_REQDBG, LOG_DEBUG,
+ 'CODE_FILE=policy.lua', 'CODE_LINE=', 'CODE_FUNC=', -- no meaningful locations
+ '[%-6s]%s', LOG_GRP_REQDBG_TAG, line)
+ end
+ end
+ end)
+ ffi.gc(debug_finish_cb, function (func) func:free() end)
+
+ return function (state, req)
+ req:vars()['policy_debug_stash'] = {}
+ policy.QTRACE(state, req)
+ req:trace_chain_callbacks(debug_stashlog_cb, debug_finish_cb)
+ return
+ end
+end
+
+policy.DEBUG_CACHE_MISS = policy.DEBUG_IF(
+ function(req)
+ return not req:all_from_cache()
+ end
+)
+
+policy.DENY = policy.DENY_MSG() -- compatibility with < 2.0
+
+function policy.DROP(_, req)
+ local answer = answer_clear(req)
+ if answer == nil then return nil end
+ req:set_extended_error(kres.extended_error.PROHIBITED, "U5KL")
+ log_policy_action(req, 'DROP')
+ return kres.FAIL
+end
+
+function policy.NO_ANSWER(_, req)
+ req.options.NO_ANSWER = true
+ log_policy_action(req, 'NO_ANSWER')
+ 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)
+ req:set_extended_error(kres.extended_error.PROHIBITED, "EIM4")
+ log_policy_action(req, 'REFUSE')
+ return kres.DONE
+end
+
+function policy.TC(state, req)
+ -- 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)
+ log_policy_action(req, 'TC')
+ return kres.DONE
+end
+
+function policy.QTRACE(_, req)
+ local qry = req:current()
+ req.options.TRACE = true
+ qry.flags.TRACE = true
+ return -- this allows to continue iterating over policy list
+end
+
+-- Evaluate packet in given rules to determine policy action
+function policy.evaluate(rules, req, query, state)
+ for i = 1, #rules do
+ local rule = rules[i]
+ if not rule.suspended then
+ local action = rule.cb(req, query)
+ if action ~= nil then
+ rule.count = rule.count + 1
+ local next_state = action(state, req)
+ if next_state then -- Not a chain rule,
+ return next_state -- stop on first match
+ end
+ end
+ end
+ end
+ return
+end
+
+-- Add rule to policy list
+function policy.add(rule, postrule)
+ -- Compatibility with 1.0.0 API
+ -- it will be dropped in 1.2.0
+ if rule == policy then
+ rule = postrule
+ postrule = nil
+ end
+ -- End of compatibility shim
+ local desc = {id=getruleid(), cb=rule, count=0}
+ table.insert(postrule and policy.postrules or policy.rules, desc)
+ return desc
+end
+
+-- Remove rule from a list
+local function delrule(rules, id)
+ for i, r in ipairs(rules) do
+ if r.id == id then
+ table.remove(rules, i)
+ return true
+ end
+ end
+ return false
+end
+
+-- Delete rule from policy list
+function policy.del(id)
+ if not delrule(policy.rules, id) then
+ if not delrule(policy.postrules, id) then
+ return false
+ end
+ end
+ return true
+end
+
+-- Convert list of string names to domain names
+function policy.todnames(names)
+ for i, v in ipairs(names) do
+ names[i] = kres.str2dname(v)
+ end
+ return names
+end
+
+-- RFC1918 Private, local, broadcast, test and special zones
+-- Considerations: RFC6761, sec 6.1.
+-- https://www.iana.org/assignments/locally-served-dns-zones
+local private_zones = {
+ -- RFC6303
+ '10.in-addr.arpa.',
+ '16.172.in-addr.arpa.',
+ '17.172.in-addr.arpa.',
+ '18.172.in-addr.arpa.',
+ '19.172.in-addr.arpa.',
+ '20.172.in-addr.arpa.',
+ '21.172.in-addr.arpa.',
+ '22.172.in-addr.arpa.',
+ '23.172.in-addr.arpa.',
+ '24.172.in-addr.arpa.',
+ '25.172.in-addr.arpa.',
+ '26.172.in-addr.arpa.',
+ '27.172.in-addr.arpa.',
+ '28.172.in-addr.arpa.',
+ '29.172.in-addr.arpa.',
+ '30.172.in-addr.arpa.',
+ '31.172.in-addr.arpa.',
+ '168.192.in-addr.arpa.',
+ '0.in-addr.arpa.',
+ '254.169.in-addr.arpa.',
+ '2.0.192.in-addr.arpa.',
+ '100.51.198.in-addr.arpa.',
+ '113.0.203.in-addr.arpa.',
+ '255.255.255.255.in-addr.arpa.',
+ -- RFC7793
+ '64.100.in-addr.arpa.',
+ '65.100.in-addr.arpa.',
+ '66.100.in-addr.arpa.',
+ '67.100.in-addr.arpa.',
+ '68.100.in-addr.arpa.',
+ '69.100.in-addr.arpa.',
+ '70.100.in-addr.arpa.',
+ '71.100.in-addr.arpa.',
+ '72.100.in-addr.arpa.',
+ '73.100.in-addr.arpa.',
+ '74.100.in-addr.arpa.',
+ '75.100.in-addr.arpa.',
+ '76.100.in-addr.arpa.',
+ '77.100.in-addr.arpa.',
+ '78.100.in-addr.arpa.',
+ '79.100.in-addr.arpa.',
+ '80.100.in-addr.arpa.',
+ '81.100.in-addr.arpa.',
+ '82.100.in-addr.arpa.',
+ '83.100.in-addr.arpa.',
+ '84.100.in-addr.arpa.',
+ '85.100.in-addr.arpa.',
+ '86.100.in-addr.arpa.',
+ '87.100.in-addr.arpa.',
+ '88.100.in-addr.arpa.',
+ '89.100.in-addr.arpa.',
+ '90.100.in-addr.arpa.',
+ '91.100.in-addr.arpa.',
+ '92.100.in-addr.arpa.',
+ '93.100.in-addr.arpa.',
+ '94.100.in-addr.arpa.',
+ '95.100.in-addr.arpa.',
+ '96.100.in-addr.arpa.',
+ '97.100.in-addr.arpa.',
+ '98.100.in-addr.arpa.',
+ '99.100.in-addr.arpa.',
+ '100.100.in-addr.arpa.',
+ '101.100.in-addr.arpa.',
+ '102.100.in-addr.arpa.',
+ '103.100.in-addr.arpa.',
+ '104.100.in-addr.arpa.',
+ '105.100.in-addr.arpa.',
+ '106.100.in-addr.arpa.',
+ '107.100.in-addr.arpa.',
+ '108.100.in-addr.arpa.',
+ '109.100.in-addr.arpa.',
+ '110.100.in-addr.arpa.',
+ '111.100.in-addr.arpa.',
+ '112.100.in-addr.arpa.',
+ '113.100.in-addr.arpa.',
+ '114.100.in-addr.arpa.',
+ '115.100.in-addr.arpa.',
+ '116.100.in-addr.arpa.',
+ '117.100.in-addr.arpa.',
+ '118.100.in-addr.arpa.',
+ '119.100.in-addr.arpa.',
+ '120.100.in-addr.arpa.',
+ '121.100.in-addr.arpa.',
+ '122.100.in-addr.arpa.',
+ '123.100.in-addr.arpa.',
+ '124.100.in-addr.arpa.',
+ '125.100.in-addr.arpa.',
+ '126.100.in-addr.arpa.',
+ '127.100.in-addr.arpa.',
+
+ -- RFC6303
+ -- localhost_reversed handles ::1
+ '0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.',
+ 'd.f.ip6.arpa.',
+ '8.e.f.ip6.arpa.',
+ '9.e.f.ip6.arpa.',
+ 'a.e.f.ip6.arpa.',
+ 'b.e.f.ip6.arpa.',
+ '8.b.d.0.1.0.0.2.ip6.arpa.',
+ -- RFC8375
+ 'home.arpa.',
+}
+policy.todnames(private_zones)
+
+-- @var Default rules
+policy.rules = {}
+policy.postrules = {}
+policy.special_names = {
+ -- XXX: beware of special_names_optim() when modifying these filters
+ {
+ cb=policy.suffix_common(policy.DENY_MSG(
+ 'Blocking is mandated by standards, see references on '
+ .. 'https://www.iana.org/assignments/'
+ .. 'locally-served-dns-zones/locally-served-dns-zones.xhtml',
+ kres.extended_error.NOTSUP),
+ private_zones, todname('arpa.')),
+ count=0
+ },
+ {
+ cb=policy.suffix(policy.DENY_MSG(
+ 'Blocking is mandated by standards, see references on '
+ .. 'https://www.iana.org/assignments/'
+ .. 'special-use-domain-names/special-use-domain-names.xhtml',
+ kres.extended_error.NOTSUP),
+ {
+ todname('test.'),
+ todname('onion.'),
+ todname('invalid.'),
+ todname('local.'), -- RFC 8375.4
+ }),
+ count=0
+ },
+ {
+ cb=policy.suffix(localhost, {dname_localhost}),
+ count=0
+ },
+ {
+ cb=policy.suffix_common(localhost_reversed, {
+ todname('127.in-addr.arpa.'),
+ todname('1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.')},
+ todname('arpa.')),
+ count=0
+ },
+}
+
+-- Return boolean; false = no special name may apply, true = some might apply.
+-- The point is to *efficiently* filter almost all QNAMEs that do not apply.
+local function special_names_optim(req, sname)
+ local qname_size = req.qsource.packet.qname_size
+ if qname_size < 9 then return true end -- don't want to special-case bad array access
+ local root = sname + qname_size - 1
+ return
+ -- .a???. or .t???.
+ (root[-5] == 4 and (root[-4] == 97 or root[-4] == 116))
+ -- .on???. or .in?????. or lo???. or *ost.
+ or (root[-6] == 5 and root[-5] == 111 and root[-4] == 110)
+ or (root[-8] == 7 and root[-7] == 105 and root[-6] == 110)
+ or (root[-6] == 5 and root[-5] == 108 and root[-4] == 111)
+ or (root[-3] == 111 and root[-2] == 115 and root[-1] == 116)
+end
+
+-- Top-down policy list walk until we hit a match
+-- the caller is responsible for reordering policy list
+-- from most specific to least specific.
+-- Some rules may be chained, in this case they are evaluated
+-- as a dependency chain, e.g. r1,r2,r3 -> r3(r2(r1(state)))
+policy.layer = {
+ begin = function(state, req)
+ -- Don't act on "finished" cases.
+ if bit.band(state, bit.bor(kres.FAIL, kres.DONE)) ~= 0 then return state end
+ local qry = req:initial() -- same as :current() but more descriptive
+ return policy.evaluate(policy.rules, req, qry, state)
+ or (special_names_optim(req, qry.sname)
+ and policy.evaluate(policy.special_names, req, qry, state))
+ or state
+ end,
+ finish = function(state, req)
+ -- Optimization for the typical case
+ if #policy.postrules == 0 then return state end
+ -- Don't act on failed cases.
+ if bit.band(state, kres.FAIL) ~= 0 then return state end
+ return policy.evaluate(policy.postrules, req, req:initial(), state) or state
+ end
+}
+
+return policy
diff --git a/modules/policy/policy.rpz.test.lua b/modules/policy/policy.rpz.test.lua
new file mode 100644
index 0000000..94fb9ce
--- /dev/null
+++ b/modules/policy/policy.rpz.test.lua
@@ -0,0 +1,65 @@
+
+local function prepare_cache()
+ cache.open(100*MB)
+ cache.clear()
+
+ local ffi = require('ffi')
+ local c = kres.context().cache
+
+ local passthru_addr = '\127\0\0\9'
+ rr_passthru = kres.rrset(todname('rpzpassthru.'), kres.type.A, kres.class.IN, 2147483647)
+ assert(rr_passthru:add_rdata(passthru_addr, #passthru_addr))
+ assert(c:insert(rr_passthru, 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_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, {})
+ check_answer('"CNAME *. on wildcard" return NODATA',
+ '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',
+ 'rpzpassthru.', kres.type.A, kres.rcode.NOERROR, '127.0.0.9')
+ check_answer('"A 192.168.5.5" return local A rrset',
+ 'rra.', kres.type.A, kres.rcode.NOERROR, '192.168.5.5')
+ check_answer('"A 192.168.6.6" with suffixed zone name in owner return local A rrset',
+ 'rra-zonename-suffix.', kres.type.A, kres.rcode.NOERROR, '192.168.6.6')
+ 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, {})
+ 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
+
+local function test_rpz_soa()
+ check_answer('"CNAME ." return NXDOMAIN (SOA origin)',
+ 'nxdomain-fqdn.', kres.type.A, kres.rcode.NXDOMAIN)
+ check_answer('"CNAME *." return NODATA (SOA origin)',
+ 'nodata-fqdn.', kres.type.A, kres.rcode.NOERROR, {})
+end
+
+net.ipv4 = false
+net.ipv6 = false
+
+prepare_cache()
+
+policy.add(policy.rpz(policy.DENY, 'policy.test.rpz'))
+policy.add(policy.rpz(policy.DENY, 'policy.test.rpz.soa'))
+
+return {
+ test_rpz,
+ test_rpz_soa,
+}
diff --git a/modules/policy/policy.slice.test.lua b/modules/policy/policy.slice.test.lua
new file mode 100644
index 0000000..89c1b05
--- /dev/null
+++ b/modules/policy/policy.slice.test.lua
@@ -0,0 +1,109 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- check lua-psl is available
+local has_psl = pcall(require, 'psl')
+if not has_psl then
+ os.exit(77) -- SKIP policy.slice
+end
+
+-- unload modules which are not related to this test
+if ta_update then
+ modules.unload('ta_update')
+end
+if ta_signal_query then
+ modules.unload('ta_signal_query')
+end
+if priming then
+ modules.unload('priming')
+end
+if detect_time_skew then
+ modules.unload('detect_time_skew')
+end
+
+local kres = require('kres')
+
+local slice_queries = {
+ {},
+ {},
+ {},
+}
+
+local function sliceaction(index)
+ return function(_, req)
+ -- log query
+ local qry = req:current()
+ local name = kres.dname2str(qry:name())
+ local count = slice_queries[index][name]
+ if not count then
+ count = 0
+ end
+ slice_queries[index][name] = count + 1
+
+ -- refuse query
+ local answer = req:ensure_answer()
+ if answer == nil then return nil end
+ answer:rcode(kres.rcode.REFUSED)
+ answer:ad(false)
+ return kres.DONE
+ end
+end
+
+-- configure slicing
+policy.add(policy.slice(
+ policy.slice_randomize_psl(0),
+ sliceaction(1),
+ sliceaction(2),
+ sliceaction(3)
+))
+
+local function check_slice(desc, qname, qtype, expected_slice, expected_count)
+ callback = function()
+ count = slice_queries[expected_slice][qname]
+ qtype_str = kres.tostring.type[qtype]
+ same(count, expected_count, desc .. qname .. ' ' .. qtype_str)
+ end
+ resolve(qname, qtype, kres.class.IN, {}, callback)
+end
+
+local function test_randomize_psl()
+ local desc = 'randomize_psl() same qname, different qtype (same slice): '
+ check_slice(desc, 'example.com.', kres.type.A, 2, 1)
+ check_slice(desc, 'example.com.', kres.type.AAAA, 2, 2)
+ check_slice(desc, 'example.com.', kres.type.MX, 2, 3)
+ check_slice(desc, 'example.com.', kres.type.NS, 2, 4)
+
+ desc = 'randomize_psl() subdomain in same slice: '
+ check_slice(desc, 'a.example.com.', kres.type.A, 2, 1)
+ check_slice(desc, 'b.example.com.', kres.type.A, 2, 1)
+ check_slice(desc, 'c.example.com.', kres.type.A, 2, 1)
+ check_slice(desc, 'a.a.example.com.', kres.type.A, 2, 1)
+ check_slice(desc, 'a.a.a.example.com.', kres.type.A, 2, 1)
+
+ desc = 'randomize_psl() different qnames in different slices: '
+ check_slice(desc, 'example2.com.', kres.type.A, 1, 1)
+ check_slice(desc, 'example5.com.', kres.type.A, 3, 1)
+
+ desc = 'randomize_psl() check unregistrable domains: '
+ check_slice(desc, '.', kres.type.A, 3, 1)
+ check_slice(desc, 'com.', kres.type.A, 1, 1)
+ check_slice(desc, 'cz.', kres.type.A, 2, 1)
+ check_slice(desc, 'co.uk.', kres.type.A, 1, 1)
+
+ desc = 'randomize_psl() check multi-level reg. domains: '
+ check_slice(desc, 'example.co.uk.', kres.type.A, 3, 1)
+ check_slice(desc, 'a.example.co.uk.', kres.type.A, 3, 1)
+ check_slice(desc, 'b.example.co.uk.', kres.type.MX, 3, 1)
+ check_slice(desc, 'example2.co.uk.', kres.type.A, 2, 1)
+
+ desc = 'randomize_psl() reg. domain - always ends up in slice: '
+ check_slice(desc, 'fdsnnsdfvkdn.com.', kres.type.A, 3, 1)
+ check_slice(desc, 'bdfbd.cz.', kres.type.A, 1, 1)
+ check_slice(desc, 'nrojgvn.net.', kres.type.A, 1, 1)
+ check_slice(desc, 'jnojtnbv.engineer.', kres.type.A, 2, 1)
+ check_slice(desc, 'dfnjonfdsjg.gov.', kres.type.A, 1, 1)
+ check_slice(desc, 'okfjnosdfgjn.mil.', kres.type.A, 1, 1)
+ check_slice(desc, 'josdhnojn.test.', kres.type.A, 2, 1)
+end
+
+return {
+ test_randomize_psl,
+}
diff --git a/modules/policy/policy.test.lua b/modules/policy/policy.test.lua
new file mode 100644
index 0000000..69dda1f
--- /dev/null
+++ b/modules/policy/policy.test.lua
@@ -0,0 +1,145 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- setup resolver
+-- policy module should be loaded by default, do not load it explicitly
+
+-- 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
+
+-- test for default configuration
+local function test_tls_forward()
+ boom(policy.TLS_FORWARD, {}, 'TLS_FORWARD without arguments')
+ boom(policy.TLS_FORWARD, {'1'}, 'TLS_FORWARD with non-table argument')
+ boom(policy.TLS_FORWARD, {{}}, 'TLS_FORWARD with empty table')
+ boom(policy.TLS_FORWARD, {{{}}}, 'TLS_FORWARD with empty target table')
+ boom(policy.TLS_FORWARD, {{{bleble=''}}}, 'TLS_FORWARD with invalid parameters in table')
+
+ boom(policy.TLS_FORWARD, {{'1'}}, 'TLS_FORWARD with invalid IP address')
+ boom(policy.TLS_FORWARD, {{{'::1', bleble=''}}}, 'TLS_FORWARD with valid IP and invalid parameters')
+ boom(policy.TLS_FORWARD, {{{'127.0.0.1'}}}, 'TLS_FORWARD with missing auth parameters')
+
+ ok(policy.TLS_FORWARD({{'127.0.0.1', insecure=true}}), 'TLS_FORWARD with no authentication')
+ boom(policy.TLS_FORWARD, {{{'100:dead::', insecure=true},
+ {'100:DEAD:0::', insecure=true}
+ }}, 'TLS_FORWARD with duplicate IP addresses is not allowed')
+ ok(policy.TLS_FORWARD({{'100:dead::2', insecure=true},
+ {'100:dead::2@443', insecure=true}
+ }), 'TLS_FORWARD with duplicate IP addresses but different ports is allowed')
+ ok(policy.TLS_FORWARD({{'100:dead::3', insecure=true},
+ {'100:beef::3', insecure=true}
+ }), 'TLS_FORWARD with different IPv6 addresses is allowed')
+ ok(policy.TLS_FORWARD({{'127.0.0.1', insecure=true},
+ {'127.0.0.2', insecure=true}
+ }), 'TLS_FORWARD with different IPv4 addresses is allowed')
+
+ boom(policy.TLS_FORWARD, {{{'::1', pin_sha256=''}}}, 'TLS_FORWARD with empty pin_sha256')
+ boom(policy.TLS_FORWARD, {{{'::1', pin_sha256='Ä'}}}, 'TLS_FORWARD with bad pin_sha256')
+ boom(policy.TLS_FORWARD, {{{'::1', pin_sha256='d161VN6aMSSdRN/TSDP6HZOHdaqcIvISlyFB9xLbGg='}}},
+ 'TLS_FORWARD with bad pin_sha256 (short base64)')
+ boom(policy.TLS_FORWARD, {{{'::1', pin_sha256='bbd161VN6aMSSdRN/TSDP6HZOHdaqcIvISlyFB9xLbGg='}}},
+ 'TLS_FORWARD with bad pin_sha256 (long base64)')
+ ok(policy.TLS_FORWARD({
+ {'::1', pin_sha256='g1PpXsxqPchz2tH6w9kcvVXqzQ0QclhInFP2+VWOqic='}
+ }), 'TLS_FORWARD with base64 pin_sha256')
+ ok(policy.TLS_FORWARD({
+ {'::1', pin_sha256={
+ 'ev1xcdU++dY9BlcX0QoKeaUftvXQvNIz/PCss1Z/3ek=',
+ 'SgnqTFcvYduWX7+VUnlNFT1gwSNvQdZakH7blChIRbM=',
+ 'bd161VN6aMSSdRN/TSDP6HZOHdaqcIvISlyFB9xLbGg=',
+ }}}), 'TLS_FORWARD with a table of pins')
+
+ -- ok(policy.TLS_FORWARD({{'::1', hostname='test.', ca_file='/tmp/ca.crt'}}), 'TLS_FORWARD with hostname + CA cert')
+ ok(policy.TLS_FORWARD({{'::1', hostname='test.'}}),
+ 'TLS_FORWARD with just hostname (use system CA store)')
+ boom(policy.TLS_FORWARD, {{{'::1', ca_file='/tmp/ca.crt'}}},
+ 'TLS_FORWARD with just CA cert')
+ boom(policy.TLS_FORWARD, {{{'::1', hostname='', ca_file='/tmp/ca.crt'}}},
+ 'TLS_FORWARD with empty hostname + CA cert')
+ boom(policy.TLS_FORWARD, {
+ {{'::1', hostname='test.', ca_file='/dev/a_file_which_surely_does_NOT_exist!'}}
+ }, 'TLS_FORWARD with hostname + unreadable CA cert')
+
+end
+
+local function test_slice()
+ boom(policy.slice, {function() end}, 'policy.slice() without any action')
+ ok(policy.slice, {function() end, policy.FORWARD, policy.FORWARD})
+end
+
+local function mirror_parser(srv, cv, nqueries)
+ local ffi = require('ffi')
+ local test_end = 0
+ local TIMEOUT = 5 -- seconds
+
+ while true do
+ local input = srv:xread('*a', 'bn', TIMEOUT)
+ if not input then
+ cv:signal()
+ return false, 'mirror: timeout'
+ end
+ --print(#input, input)
+ -- convert query to knot_pkt_t
+ local wire = ffi.cast("void *", input)
+ local pkt = ffi.gc(ffi.C.knot_pkt_new(wire, #input, nil), ffi.C.knot_pkt_free)
+ if not pkt then
+ cv:signal()
+ return false, 'mirror: packet allocation error'
+ end
+
+ local result = ffi.C.knot_pkt_parse(pkt, 0)
+ if result ~= 0 then
+ cv:signal()
+ return false, 'mirror: packet parse error'
+ end
+ --print(pkt)
+ test_end = test_end + 1
+
+ if test_end == nqueries then
+ cv:signal()
+ return true, 'packet mirror pass'
+ end
+
+ end
+end
+
+local function test_mirror()
+ local kluautil = require('kluautil')
+ local socket = require('cqueues.socket')
+ local cond = require('cqueues.condition')
+ local cv = cond.new()
+ local queries = {}
+ local srv = socket.listen({
+ host = "127.0.0.1",
+ port = 36659,
+ type = socket.SOCK_DGRAM,
+ })
+ -- binary mode, no buffering
+ srv:setmode('bn', 'bn')
+
+ queries["bla.mujtest.cz."] = kres.type.AAAA
+ queries["bla.mujtest2.cz."] = kres.type.AAAA
+
+ -- UDP server for test
+ worker.bg_worker.cq:wrap(function()
+ local err, msg = mirror_parser(srv, cv, kluautil.kr_table_len(queries))
+
+ ok(err, msg)
+ end)
+
+ policy.add(policy.suffix(policy.MIRROR('127.0.0.1@36659'), policy.todnames({'mujtest.cz.'})))
+ policy.add(policy.suffix(policy.MIRROR('127.0.0.1@36659'), policy.todnames({'mujtest2.cz.'})))
+
+ for name, rtype in pairs(queries) do
+ resolve(name, rtype)
+ end
+
+ cv:wait()
+end
+
+return {
+ test_tls_forward,
+ test_mirror,
+ test_slice,
+}
diff --git a/modules/policy/policy.test.rpz b/modules/policy/policy.test.rpz
new file mode 100644
index 0000000..d962e9f
--- /dev/null
+++ b/modules/policy/policy.test.rpz
@@ -0,0 +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 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 --git a/modules/policy/policy.test.rpz.soa b/modules/policy/policy.test.rpz.soa
new file mode 100644
index 0000000..ad18aa4
--- /dev/null
+++ b/modules/policy/policy.test.rpz.soa
@@ -0,0 +1,5 @@
+test2domain. SOA nonexistent.test2domain. test2domain. 1 12h 15m 3w 2h
+ NS nonexistent.test2domain.
+
+nxdomain-fqdn.test2domain. CNAME .
+nodata-fqdn.test2domain. CNAME *.
diff --git a/modules/policy/test.integr/deckard.yaml b/modules/policy/test.integr/deckard.yaml
new file mode 100644
index 0000000..9c6cb70
--- /dev/null
+++ b/modules/policy/test.integr/deckard.yaml
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+programs:
+- name: kresd
+ binary: kresd
+ additional:
+ - --noninteractive
+ templates:
+ - modules/policy/test.integr/kresd_config.j2
+ - tests/integration/hints_zone.j2
+ configs:
+ - config
+ - hints
diff --git a/modules/policy/test.integr/kresd_config.j2 b/modules/policy/test.integr/kresd_config.j2
new file mode 100644
index 0000000..668c792
--- /dev/null
+++ b/modules/policy/test.integr/kresd_config.j2
@@ -0,0 +1,59 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+{% raw %}
+policy.add(policy.domains(policy.DENY, {todname('example.com')}))
+policy.add(policy.suffix(policy.REFUSE, {todname('refuse.example.com')}))
+
+-- make sure DNSSEC is turned off for tests
+trust_anchors.remove('.')
+
+-- 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
+
+_hint_root_file('hints')
+cache.size = 2*MB
+log_level('debug')
+{% endraw %}
+
+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(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 --git a/modules/policy/test.integr/refuse.rpl b/modules/policy/test.integr/refuse.rpl
new file mode 100644
index 0000000..08f9942
--- /dev/null
+++ b/modules/policy/test.integr/refuse.rpl
@@ -0,0 +1,44 @@
+; SPDX-License-Identifier: GPL-3.0-or-later
+; config options
+ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test refuse policy
+
+STEP 10 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+www.refuse.example.com. IN A
+ENTRY_END
+
+STEP 20 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer
+; AD must not be set in the answer
+REPLY QR RD RA REFUSED
+SECTION QUESTION
+www.refuse.example.com. IN A
+SECTION ANSWER
+ENTRY_END
+
+STEP 30 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+example.com. IN A
+ENTRY_END
+
+STEP 40 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer
+REPLY QR RD AA RA NXDOMAIN
+SECTION QUESTION
+example.com. IN A
+SECTION ANSWER
+SECTION AUTHORITY
+example.com. 10800 IN SOA example.com. nobody.invalid. 1 3600 1200 604800 10800
+ENTRY_END
+
+
+SCENARIO_END
diff --git a/modules/predict/.packaging/test.config b/modules/predict/.packaging/test.config
new file mode 100644
index 0000000..b8e706e
--- /dev/null
+++ b/modules/predict/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('predict')
+assert(predict)
+quit()
diff --git a/modules/predict/README.rst b/modules/predict/README.rst
new file mode 100644
index 0000000..966c4ca
--- /dev/null
+++ b/modules/predict/README.rst
@@ -0,0 +1,67 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-predict:
+
+Prefetching records
+===================
+
+The ``predict`` module helps to keep the cache hot by prefetching records.
+It can utilize two independent mechanisms to select the records which should be refreshed:
+expiring records and prediction.
+
+Expiring records
+----------------
+
+This mechanism is always active when the predict module is loaded and it is not configurable.
+
+Any time the resolver answers with records that are about to expire,
+they get refreshed. (see :c:func:`is_expiring`)
+That improves latency for records which get frequently queried, relatively to their TTL.
+
+Prediction
+----------
+
+The predict module can also learn usage patterns and repetitive queries,
+though this mechanism is a prototype and **not recommended** for use in production or with high traffic.
+
+For example, if it makes a query every day at 18:00,
+the resolver expects that it is needed by that time and prefetches it ahead of time.
+This is helpful to minimize the perceived latency and keeps the cache hot.
+
+You can disable prediction by configuring ``period = 0``.
+Otherwise it will load the required :ref:`stats <mod-stats>` module if not present,
+and it will use its :func:`stats.frequent` table and clear it periodically.
+
+.. tip:: The tracking window and period length determine memory requirements. If you have a server with relatively fast query turnover, keep the period low (hour for start) and shorter tracking window (5 minutes). For personal slower resolver, keep the tracking window longer (i.e. 30 minutes) and period longer (a day), as the habitual queries occur daily. Experiment to get the best results.
+
+Example configuration
+---------------------
+
+.. code-block:: lua
+
+ modules = {
+ predict = {
+ -- this mode is NOT RECOMMENDED for use in production
+ window = 15, -- 15 minutes sampling window
+ period = 6*(60/15) -- track last 6 hours
+ }
+ }
+
+Exported metrics
+----------------
+
+To visualize the efficiency of the predictions, the module exports following statistics.
+
+* ``predict.epoch`` - current prediction epoch (based on time of day and sampling window)
+* ``predict.queue`` - number of queued queries in current window
+* ``predict.learned`` - number of learned queries in current window
+
+
+Properties
+----------
+
+.. function:: predict.config({ window = 15, period = 24})
+
+ Reconfigure the predictor to given tracking window and period length. Both parameters are optional.
+ Window length is in minutes, period is a number of windows that can be kept in memory.
+ e.g. if a ``window`` is 15 minutes, a ``period`` of "24" means 6 hours.
diff --git a/modules/predict/predict.lua b/modules/predict/predict.lua
new file mode 100644
index 0000000..0117fd5
--- /dev/null
+++ b/modules/predict/predict.lua
@@ -0,0 +1,189 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- Speculative prefetching for repetitive and soon-expiring records to reduce latency.
+-- @module predict
+-- @field queue queue of scheduled queries
+-- @field queue_len number of scheduled queries
+-- @field period length of prediction history (number of windows)
+-- @field window length of the prediction window
+local predict = {
+ queue = {},
+ queue_len = 0,
+ batch = 0,
+ period = 24,
+ window = 15,
+ log = {},
+}
+
+
+-- Calculate next sample with jitter [1-2/5 of window]
+local function next_event()
+ local jitter = (predict.window * minute) / 5;
+ return math.random(jitter, 2 * jitter)
+end
+
+-- Calculate current epoch (which window fits current time)
+function predict.epoch()
+ if not predict.period or predict.period <= 1 then return nil end
+ return (os.date('%H')*(60/predict.window) +
+ math.floor(os.date('%M')/predict.window)) % predict.period + 1
+end
+
+-- Resolve queued records and flush the queue
+function predict.drain()
+ local deleted = 0
+ for key, _ in pairs(predict.queue) do
+ local qtype, qname = key:match('(%S*)%s(.*)')
+ resolve(qname, kres.type[qtype], kres.class.IN, {'NO_CACHE'})
+ predict.queue[key] = nil
+ deleted = deleted + 1
+ -- Resolve smaller batches at a time
+ if predict.batch > 0 and deleted >= predict.batch then
+ break
+ end
+ end
+ -- Schedule prefetch of another batch if not complete
+ if predict.ev_drain then event.cancel(predict.ev_drain) end
+ predict.ev_drain = nil
+ if deleted > 0 then
+ predict.ev_drain = event.after((predict.window * 3) * sec, predict.drain)
+ end
+ predict.queue_len = predict.queue_len - deleted
+ stats['predict.queue'] = predict.queue_len
+ collectgarbage('step')
+ return 0
+end
+
+-- Enqueue queries from same format as predict.queue or predict.log
+local function enqueue_from_log(current)
+ if not current then return 0 end
+ local queued = 0
+ for key, val in pairs(current) do
+ if val and not predict.queue[key] then
+ predict.queue[key] = val
+ queued = queued + 1
+ end
+ end
+ return queued
+end
+
+-- Sample current epoch, return number of sampled queries
+function predict.sample(epoch_now)
+ if not epoch_now then return 0, 0 end
+ local current = predict.log[epoch_now] or {}
+ local queries = stats.frequent()
+ stats.clear_frequent()
+ local nr_samples = #queries
+ for i = 1, nr_samples do
+ local entry = queries[i]
+ local key = string.format('%s %s', entry.type, entry.name)
+ current[key] = 1
+ end
+ predict.log[epoch_now] = current
+ return nr_samples
+end
+
+-- Predict queries for the upcoming epoch
+local function generate(epoch_now)
+ if not epoch_now then return 0 end
+ local queued = 0
+ for i = 1, predict.period / 2 - 1 do
+ local current = predict.log[(epoch_now - i - 1) % predict.period + 1]
+ local past = predict.log[(epoch_now - 2*i - 1) % predict.period + 1]
+ if current and past then
+ for k, _ in pairs(current) do
+ if past[k] ~= nil and not predict.queue[k] then
+ queued = queued + 1
+ predict.queue[k] = 1
+ end
+ end
+ end
+ end
+ return queued
+end
+
+function predict.process()
+ -- Start a new epoch, or continue sampling
+ local epoch_now = predict.epoch()
+ local nr_queued = 0
+
+ -- End of epoch
+ if predict.current_epoch ~= epoch_now then
+ stats['predict.epoch'] = epoch_now
+ predict.current_epoch = epoch_now
+ -- enqueue records from upcoming epoch
+ nr_queued = enqueue_from_log(predict.log[epoch_now])
+ -- predict next epoch
+ nr_queued = nr_queued + generate(epoch_now)
+ -- clear log for new epoch
+ predict.log[epoch_now] = {}
+ end
+
+ -- Sample current epoch
+ local nr_learned = predict.sample(epoch_now)
+
+ -- Dispatch predicted queries
+ if nr_queued > 0 then
+ predict.queue_len = predict.queue_len + nr_queued
+ predict.batch = predict.queue_len / 5
+ if not predict.ev_drain then
+ predict.ev_drain = event.after(0, predict.drain)
+ end
+ end
+
+ if predict.ev_sample then event.cancel(predict.ev_sample) end
+ predict.ev_sample = event.after(next_event(), predict.process)
+ if stats then
+ stats['predict.queue'] = predict.queue_len
+ stats['predict.learned'] = nr_learned
+ end
+ collectgarbage()
+end
+
+function predict.init()
+ if predict.window > 0 and predict.period > 0 then
+ predict.current_epoch = predict.epoch()
+ predict.ev_sample = event.after(next_event(), predict.process)
+ end
+end
+
+function predict.deinit()
+ if predict.ev_sample then event.cancel(predict.ev_sample) end
+ if predict.ev_drain then event.cancel(predict.ev_drain) end
+ predict.ev_sample = nil
+ predict.ev_drain = nil
+ predict.log = {}
+ predict.queue = {}
+ predict.queue_len = 0
+ collectgarbage()
+end
+
+function predict.config(config)
+ -- Reconfigure
+ config = config or {}
+ if type(config) ~= 'table' then
+ error('[predict] configuration must be a table or nil')
+ end
+ if config.window then predict.window = config.window end
+ if config.period then predict.period = config.period end
+ -- Load dependent modules
+ if (predict.period or 0) ~= 0 and not stats then modules.load('stats') end
+ -- Reinitialize to reset timers
+ predict.deinit()
+ predict.init()
+end
+
+predict.layer = {
+ -- Prefetch all expiring (sub-)queries immediately after the request finishes.
+ -- Doing that immediately is simplest and avoids creating (new) large bursts of activity.
+ finish = function (_, req)
+ local qrys = req.rplan.resolved
+ for i = 0, (tonumber(qrys.len) - 1) do -- size_t doesn't work for some reason
+ local qry = qrys.at[i]
+ if qry.flags.EXPIRING == true then
+ resolve(kres.dname2str(qry.sname), qry.stype, qry.sclass, {'NO_CACHE'})
+ end
+ end
+ end
+}
+
+return predict
diff --git a/modules/predict/predict.test.lua b/modules/predict/predict.test.lua
new file mode 100644
index 0000000..590b41c
--- /dev/null
+++ b/modules/predict/predict.test.lua
@@ -0,0 +1,61 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- setup resolver
+modules = { 'predict' }
+
+-- mock global functions
+local resolve_count = 0
+local current_epoch = 0
+
+resolve = function ()
+ resolve_count = resolve_count + 1
+end
+
+stats.frequent = function ()
+ return {
+ {name = 'example.com', type = 'TYPE65535'},
+ {name = 'example.com', type = 'SOA'},
+ }
+end
+
+predict.epoch = function ()
+ return current_epoch % predict.period + 1
+end
+
+-- test if draining of prefetch queue works
+local function test_predict_drain()
+ resolve_count = 0
+ predict.queue_len = 2
+ predict.queue['TYPE65535 example.com'] = 1
+ predict.queue['SOA example.com'] = 1
+ predict.drain()
+ -- test that it attempted to prefetch
+ same(resolve_count, 2, 'attempted to prefetch on drain')
+ same(predict.queue_len, 0, 'prefetch queue empty after drain')
+end
+
+-- test if prediction process works
+local function test_predict_process()
+ -- start new epoch
+ predict.process()
+ same(predict.queue_len, 0, 'first epoch, empty prefetch queue')
+ -- next epoch, still no period for frequent queries
+ current_epoch = current_epoch + 1
+ predict.process()
+ same(predict.queue_len, 0, 'second epoch, empty prefetch queue')
+ -- next epoch, found period
+ current_epoch = current_epoch + 1
+ predict.process()
+ same(predict.queue_len, 2, 'third epoch, prefetching')
+ -- drain works with scheduled prefetches (two batches)
+ resolve_count = 0
+ predict.drain()
+ predict.drain()
+ same(resolve_count, 2, 'attempted to resolve queries in queue')
+ same(predict.queue_len, 0, 'prefetch queue is empty')
+end
+
+-- return test set
+return {
+ test_predict_drain,
+ test_predict_process
+}
diff --git a/modules/prefill/.packaging/test.config b/modules/prefill/.packaging/test.config
new file mode 100644
index 0000000..d0258b0
--- /dev/null
+++ b/modules/prefill/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('prefill')
+assert(prefill)
+quit()
diff --git a/modules/prefill/README.rst b/modules/prefill/README.rst
new file mode 100644
index 0000000..a99f1f0
--- /dev/null
+++ b/modules/prefill/README.rst
@@ -0,0 +1,43 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-prefill:
+
+Cache prefilling
+================
+
+This module provides ability to periodically prefill the DNS cache by importing root zone data obtained over HTTPS.
+
+Intended users of this module are big resolver operators which will benefit from decreased latencies and smaller amount of traffic towards DNS root servers.
+
+Example configuration is:
+
+.. code-block:: lua
+
+ modules.load('prefill')
+ prefill.config({
+ ['.'] = {
+ url = 'https://www.internic.net/domain/root.zone',
+ interval = 86400, -- seconds
+ ca_file = '/etc/pki/tls/certs/ca-bundle.crt', -- optional
+ }
+ })
+
+This configuration downloads the zone file from URL `https://www.internic.net/domain/root.zone` and imports it into the cache every 86400 seconds (1 day). The HTTPS connection is authenticated using a CA certificate from file `/etc/pki/tls/certs/ca-bundle.crt` and signed zone content is validated using DNSSEC.
+
+The root zone to be imported must be signed using DNSSEC and the resolver must have a valid DNSSEC configuration.
+
+.. csv-table::
+ :header: "Parameter", "Description"
+
+ "ca_file", "path to CA certificate bundle used to authenticate the HTTPS connection (optional, system-wide store will be used if not specified)"
+ "interval", "number of seconds between zone data refresh attempts"
+ "url", "URL of a file in :rfc:`1035` zone file format"
+
+Only root zone import is supported at the moment.
+
+Dependencies
+------------
+
+Prefilling depends on the lua-http_ library.
+
+.. _lua-http: https://luarocks.org/modules/daurnimator/http
diff --git a/modules/prefill/prefill.lua b/modules/prefill/prefill.lua
new file mode 100644
index 0000000..f4a4288
--- /dev/null
+++ b/modules/prefill/prefill.lua
@@ -0,0 +1,199 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local ffi = require('ffi')
+
+local rz_url = "https://www.internic.net/domain/root.zone"
+local rz_local_fname = "root.zone"
+local rz_ca_file = nil
+local rz_event_id = nil
+
+local rz_default_interval = 86400
+local rz_https_fail_interval = 600
+local rz_import_error_interval = 600
+local rz_cur_interval = rz_default_interval
+local rz_interval_randomizer_limit = 10
+local rz_interval_threshold = 5
+local rz_interval_min = 3600
+
+local rz_first_try = true
+
+local prefill = {}
+
+-- hack for circular dependency between timer() and fill_cache()
+local forward_references = {}
+
+local function stop_timer()
+ if rz_event_id then
+ event.cancel(rz_event_id)
+ rz_event_id = nil
+ end
+end
+
+local function timer()
+ stop_timer()
+ worker.bg_worker.cq:wrap(forward_references.fill_cache)
+end
+
+local function restart_timer(after)
+ stop_timer()
+ rz_event_id = event.after(after * sec, timer)
+end
+
+local function display_delay(time)
+ local days = math.floor(time / 86400)
+ local hours = math.floor((time % 86400) / 3600)
+ local minutes = math.floor((time % 3600) / 60)
+ local seconds = math.floor(time % 60)
+ if days > 0 then
+ return string.format("%d days %02d hours", days, hours)
+ elseif hours > 0 then
+ return string.format("%02d hours %02d minutes", hours, minutes)
+ elseif minutes > 0 then
+ return string.format("%02d minutes %02d seconds", minutes, seconds)
+ end
+ return string.format("%02d seconds", seconds)
+end
+
+-- returns: number of seconds the file is valid for
+-- 0 indicates immediate download
+local function get_file_ttl(fname)
+ local mtime = tonumber(ffi.C.kr_file_mtime(fname))
+
+ if mtime > 0 then
+ local age = os.time() - mtime
+ return math.max(
+ rz_cur_interval - age,
+ 0)
+ else
+ return 0 -- file does not exist, download now
+ end
+end
+
+local function download(url, fname)
+ local kluautil = require('kluautil')
+ local file, rcode, errmsg
+ file, errmsg = io.open(fname, 'w')
+ if not file then
+ error(string.format("[prefil] unable to open file %s (%s)",
+ fname, errmsg))
+ end
+
+ log_info(ffi.C.LOG_GRP_PREFILL, "downloading root zone to file %s ...", fname)
+ rcode, errmsg = kluautil.kr_https_fetch(url, file, rz_ca_file)
+ if rcode == nil then
+ error(string.format("[prefil] fetch of `%s` failed: %s", url, errmsg))
+ end
+
+ file:close()
+end
+
+local function import(fname)
+ local ret = ffi.C.zi_zone_import({
+ zone_file = fname,
+ time_src = ffi.C.ZI_STAMP_MTIM, -- the file might be slightly older
+ })
+ if ret == 0 then
+ log_info(ffi.C.LOG_GRP_PREFILL, "zone successfully parsed, import started")
+ else
+ error(string.format(
+ "[prefil] zone import failed: %s", ffi.C.knot_strerror(ret)
+ ))
+ end
+end
+
+function forward_references.fill_cache()
+ local file_ttl = get_file_ttl(rz_local_fname)
+
+ if file_ttl > rz_interval_threshold then
+ log_info(ffi.C.LOG_GRP_PREFILL, "root zone file valid for %s, reusing data from disk",
+ display_delay(file_ttl))
+ else
+ local ok, errmsg = pcall(download, rz_url, rz_local_fname)
+ if not ok then
+ rz_cur_interval = rz_https_fail_interval
+ - math.random(rz_interval_randomizer_limit)
+ log_info(ffi.C.LOG_GRP_PREFILL, "cannot download new zone (%s), "
+ .. "will retry root zone download in %s",
+ errmsg, display_delay(rz_cur_interval))
+ restart_timer(rz_cur_interval)
+ os.remove(rz_local_fname)
+ return
+ end
+ file_ttl = rz_default_interval
+ end
+ -- file is up to date, import
+ -- import/filter function gets executed after resolver/module
+ local ok, errmsg = pcall(import, rz_local_fname)
+ if not ok then
+ if rz_first_try then
+ rz_first_try = false
+ rz_cur_interval = 1
+ else
+ rz_cur_interval = rz_import_error_interval
+ - math.random(rz_interval_randomizer_limit)
+ end
+ log_info(ffi.C.LOG_GRP_PREFILL, "root zone import failed (%s), retry in %s",
+ errmsg, display_delay(rz_cur_interval))
+ else
+ -- re-download before TTL expires
+ rz_cur_interval = (file_ttl - rz_interval_threshold
+ - math.random(rz_interval_randomizer_limit))
+ log_info(ffi.C.LOG_GRP_PREFILL, "root zone refresh in %s",
+ display_delay(rz_cur_interval))
+ end
+ restart_timer(rz_cur_interval)
+end
+
+function prefill.deinit()
+ stop_timer()
+end
+
+-- process one item from configuration table
+-- right now it supports only root zone because
+-- prefill module uses global variables
+local function config_zone(zone_cfg)
+ if zone_cfg.interval then
+ zone_cfg.interval = tonumber(zone_cfg.interval)
+ if zone_cfg.interval < rz_interval_min then
+ error(string.format('[prefil] refresh interval %d s is too short, '
+ .. 'minimal interval is %d s',
+ zone_cfg.interval, rz_interval_min))
+ end
+ rz_default_interval = zone_cfg.interval
+ rz_cur_interval = zone_cfg.interval
+ end
+
+ rz_ca_file = zone_cfg.ca_file
+
+ if not zone_cfg.url or not string.match(zone_cfg.url, '^https://') then
+ error('[prefil] option url must contain a '
+ .. 'https:// URL of a zone file')
+ else
+ rz_url = zone_cfg.url
+ end
+end
+
+function prefill.config(config)
+ if config == nil then return end -- e.g. just modules = { 'prefill' }
+ local root_configured = false
+ if type(config) ~= 'table' then
+ error('[prefil] configuration must be in table '
+ .. '{owner name = {per-zone config}}')
+ end
+ for owner, zone_cfg in pairs(config) do
+ if owner ~= '.' then
+ error('[prefil] only root zone can be imported '
+ .. 'at the moment')
+ else
+ config_zone(zone_cfg)
+ root_configured = true
+ end
+ end
+ if not root_configured then
+ error('[prefil] this module version requires configuration '
+ .. 'for root zone')
+ end
+
+ restart_timer(0) -- start now
+end
+
+return prefill
diff --git a/modules/prefill/prefill.test/empty.zone b/modules/prefill/prefill.test/empty.zone
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/modules/prefill/prefill.test/empty.zone
diff --git a/modules/prefill/prefill.test/example.com.zone b/modules/prefill/prefill.test/example.com.zone
new file mode 100644
index 0000000..55edbf0
--- /dev/null
+++ b/modules/prefill/prefill.test/example.com.zone
@@ -0,0 +1,12 @@
+; SPDX-License-Identifier: GPL-3.0-or-later
+$ORIGIN example.com.
+$TTL 3600
+
+@ SOA dns1.example.com. hostmaster.example.com. (
+ 2010111213 ; serial
+ 6h ; refresh
+ 1h ; retry
+ 1w ; expire
+ 1d ) ; minimum
+
+ NS dns1
diff --git a/modules/prefill/prefill.test/prefill.test.lua b/modules/prefill/prefill.test/prefill.test.lua
new file mode 100644
index 0000000..3d39d8f
--- /dev/null
+++ b/modules/prefill/prefill.test/prefill.test.lua
@@ -0,0 +1,123 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- unload modules which are not related to this test
+if ta_signal_query then
+ modules.unload('ta_signal_query')
+end
+if priming then
+ modules.unload('priming')
+end
+if detect_time_skew then
+ modules.unload('detect_time_skew')
+end
+
+-- test. domain is used by some tests, allow it
+policy.add(policy.suffix(policy.PASS, {todname('test.')}))
+
+cache.size = 2*MB
+-- log_level('debug')
+
+-- 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
+assert(#modules.list() > 0)
+-- Self-check timers
+ev = event.recurrent(1 * sec, function () return 1 end)
+event.cancel(ev)
+ev = event.after(0, function () return 1 end)
+
+
+-- Import fake root zone; avoid interference with configured keyfile_default.
+trust_anchors.remove('.')
+trust_anchors.add('. IN DS 18213 7 2 A1D391053583A4BC597DB9588B296060FC55EAC80B3831CA371BA1FA AE997244')
+
+-- 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 zone_import(fname, downgrade)
+ return require('ffi').C.zi_zone_import({
+ zone_file = fname,
+ downgrade = downgrade,
+ })
+end
+
+local function import_valid_root_zone()
+ cache.clear()
+ local import_res = zone_import('testroot.zone')
+ assert(import_res == 0)
+ -- beware that import takes at least 100 ms
+ worker.sleep(0.2) -- zimport is delayed by 100 ms from function call
+ -- sanity checks - cache must be filled in
+ ok(cache.count() > 0, 'cache is not empty after import of valid signed root zone')
+ check_answer('root apex is in cache',
+ '.', kres.type.NS, kres.rcode.NOERROR)
+ check_answer('deep subdomain is in cache',
+ 'a.b.subtree1.', kres.type.AAAA, kres.rcode.NOERROR)
+end
+
+local function import_root_no_soa()
+ cache.clear()
+ local import_res = zone_import('testroot_no_soa.zone')
+ assert(import_res == -1)
+ -- beware that import takes at least 100 ms
+ worker.sleep(0.2) -- zimport is delayed by 100 ms from function call
+ -- sanity checks - cache must be filled in
+ ok(cache.count() == 0 , 'cache is still empty after import of zone without SOA record')
+end
+
+local function import_unsigned_root_zone()
+ cache.clear()
+ zone_import('testroot.zone.unsigned')
+ -- beware that import takes at least 100 ms
+ worker.sleep(0.2) -- zimport is delayed by 100 ms from function call
+ -- we wanted it to fail
+ ok(cache.count() == 0, 'cache is still empty after import of unsigned zone')
+end
+
+local function import_not_root_zone()
+ cache.clear()
+ zone_import('example.com.zone')
+ -- beware that import takes at least 100 ms
+ worker.sleep(0.2) -- zimport is delayed by 100 ms from function call
+ -- we wanted it to fail
+ ok(cache.count() == 0, 'cache is still empty after import of other zone than root')
+end
+
+local function import_empty_zone()
+ cache.clear()
+ local import_res = zone_import('empty.zone')
+ assert(import_res < 0)
+ -- beware that import takes at least 100 ms
+ worker.sleep(0.2) -- zimport is delayed by 100 ms from function call
+ -- sanity checks - cache must be filled in
+ ok(cache.count() == 0, 'cache is still empty after import of empty zone')
+end
+
+local function import_random_trash()
+ cache.clear()
+ local import_res = zone_import('random.zone')
+ assert(import_res < 0)
+ -- beware that import takes at least 100 ms
+ worker.sleep(0.2) -- zimport is delayed by 100 ms from function call
+ -- sanity checks - cache must be filled in
+ ok(cache.count() == 0, 'cache is still empty after import of unparseable file')
+end
+
+return {
+ import_valid_root_zone,
+ import_root_no_soa,
+ import_unsigned_root_zone,
+ import_not_root_zone,
+ import_empty_zone,
+ import_random_trash,
+}
diff --git a/modules/prefill/prefill.test/random.zone b/modules/prefill/prefill.test/random.zone
new file mode 100644
index 0000000..1650ac2
--- /dev/null
+++ b/modules/prefill/prefill.test/random.zone
@@ -0,0 +1,2 @@
+4ƒ…Óð=g<¨ú¦£k~åŸÌ>BËiÚ´= FN5—0H.ÚÄÃaÅ@æ±wUë†Ù¼’ûPné2Þ—t}‡³3Éqì©U•ž‰¡lÊΤQIa  ©y¦GíD#¿ ÈÖˆ>Ž»SdjÿÿU?ʨïTö…ÒâWMC}2 )Ø`a’œ®© *lj7Vº5¹%圅.• þü˜eZ5BI”‹òSÞšÙÉÑLv>Šþ|óœï­<dFß;ß6ï¾ ÂàG´¥L{tɹÀÝŒ*C¬÷cj$»G)­ÈÛIáœïCÿ0¦}ÏtÐãåNKþ¾^Íßd±ÐØÚ
+; SPDX-License-Identifier: GPL-3.0-or-later
diff --git a/modules/prefill/prefill.test/testroot.zone b/modules/prefill/prefill.test/testroot.zone
new file mode 100644
index 0000000..76b9d18
--- /dev/null
+++ b/modules/prefill/prefill.test/testroot.zone
@@ -0,0 +1,59 @@
+; SPDX-License-Identifier: GPL-3.0-or-later
+; File written on Thu Aug 22 15:47:02 2019
+; dnssec_signzone version 9.11.3-1ubuntu1.8-Ubuntu
+. 86400 IN SOA rootns. you.test. (
+ 2017071102 ; serial
+ 1800 ; refresh (30 minutes)
+ 900 ; retry (15 minutes)
+ 604800 ; expire (1 week)
+ 86400 ; minimum (1 day)
+ )
+ 86400 RRSIG SOA 7 0 86400 (
+ 20500101000000 20190822124702 46349 .
+ QRmlVBvwhkNqmsp4H9zxcPeVg++5g/eR8EPb
+ DldazjUIoqbQYarTD+3DDf8tvwJu1aBNvBhm
+ cQCauTS5JWzisg== )
+ 86400 NS rootns.
+ 86400 RRSIG NS 7 0 86400 (
+ 20500101000000 20190822124702 46349 .
+ kMiL+id0WrESTSw51qI96kbolLTegn+Uraim
+ 8GjNr0d2fH8m885ORkr7C4g0RrzfAKNokArF
+ rQltwL8sMowgJg== )
+ 86400 NSEC a.b.subtree1. NS SOA RRSIG NSEC DNSKEY
+ 86400 RRSIG NSEC 7 0 86400 (
+ 20500101000000 20190822124702 46349 .
+ zv+f8FELPfYeWn7Ryy/+rBR+qASu4QC6gAka
+ vpeWRgybUQh50LrJIxINuf0YLCpqxjsX6zkK
+ zwbt0BgPHjfRHA== )
+ 86400 DNSKEY 256 3 7 (
+ AwEAAc9BtlREycexYH5az+dIbhI6sM56F+kd
+ SI43ZTGNT/Bam5vGrXju0uTHCJ2+KBwOSz7d
+ ZVchX0ulIJOUV9MteT0=
+ ) ; ZSK; alg = NSEC3RSASHA1 ; key id = 46349
+ 86400 DNSKEY 257 3 7 (
+ AwEAAcEFKHPyE1JMfRLhJK9mgcBZ+TR0Pj6u
+ shF6YbLkQoRs6Uzm458ErCcAdukJsTckqCzq
+ PFEqGLRztzyAJ7Z3G0k=
+ ) ; KSK; alg = NSEC3RSASHA1 ; key id = 18213
+ 86400 RRSIG DNSKEY 7 0 86400 (
+ 20500101000000 20190822124702 18213 .
+ ih4ScNqt9muT/Dqc05oO5T/xAyRK1/LblHph
+ GHedPHW7mC6IzsDBbqjD/P1nVK5RkM2Q+ozV
+ Ltbtmt2CafXsLA== )
+ 86400 RRSIG DNSKEY 7 0 86400 (
+ 20500101000000 20190822124702 46349 .
+ Lqle63cGoJdZA4CHHUq3ZqFxsbYATelzj5Dl
+ lDcc7vLbn2Qy9AVUC5I6UdZqMK2UDyO+DWCG
+ Cmq5705eAQlcsQ== )
+a.b.subtree1. 86400 IN AAAA 2001:db8::
+ 86400 RRSIG AAAA 7 3 86400 (
+ 20500101000000 20190822124702 46349 .
+ Hq6CUu3CVN/90b1Sozv0uIgH5ePxY3olc3eq
+ PoeyfdS+3HjSgb+Ji+GjYAAOMaVDS0APwwMe
+ pHxhdgO/zpKHRQ== )
+ 86400 NSEC . AAAA RRSIG NSEC
+ 86400 RRSIG NSEC 7 3 86400 (
+ 20500101000000 20190822124702 46349 .
+ lcJ7xdzxgTTvj2JiwzhDRyxTx2ZJ5zwzx0hC
+ ttTrSfG+2GyMnPzJ9MFid5S2w0WbWOWWLaKH
+ O0ucI8xvYInNAA== )
diff --git a/modules/prefill/prefill.test/testroot.zone.unsigned b/modules/prefill/prefill.test/testroot.zone.unsigned
new file mode 100644
index 0000000..fbc551f
--- /dev/null
+++ b/modules/prefill/prefill.test/testroot.zone.unsigned
@@ -0,0 +1,4 @@
+; SPDX-License-Identifier: GPL-3.0-or-later
+. 86400 SOA rootns. you.test. 2017071101 1800 900 604800 86400
+. 86400 NS rootns.
+a.b.subtree1. 86400 AAAA 2001:db8::
diff --git a/modules/prefill/prefill.test/testroot_no_soa.zone b/modules/prefill/prefill.test/testroot_no_soa.zone
new file mode 100644
index 0000000..e3ad08c
--- /dev/null
+++ b/modules/prefill/prefill.test/testroot_no_soa.zone
@@ -0,0 +1,52 @@
+; SPDX-License-Identifier: GPL-3.0-or-later
+; File written on Thu Aug 22 15:47:02 2019
+; dnssec_signzone version 9.11.3-1ubuntu1.8-Ubuntu
+. 86400 RRSIG SOA 7 0 86400 (
+ 20500101000000 20190822124702 46349 .
+ QRmlVBvwhkNqmsp4H9zxcPeVg++5g/eR8EPb
+ DldazjUIoqbQYarTD+3DDf8tvwJu1aBNvBhm
+ cQCauTS5JWzisg== )
+ 86400 NS rootns.
+ 86400 RRSIG NS 7 0 86400 (
+ 20500101000000 20190822124702 46349 .
+ kMiL+id0WrESTSw51qI96kbolLTegn+Uraim
+ 8GjNr0d2fH8m885ORkr7C4g0RrzfAKNokArF
+ rQltwL8sMowgJg== )
+ 86400 NSEC a.b.subtree1. NS SOA RRSIG NSEC DNSKEY
+ 86400 RRSIG NSEC 7 0 86400 (
+ 20500101000000 20190822124702 46349 .
+ zv+f8FELPfYeWn7Ryy/+rBR+qASu4QC6gAka
+ vpeWRgybUQh50LrJIxINuf0YLCpqxjsX6zkK
+ zwbt0BgPHjfRHA== )
+ 86400 DNSKEY 256 3 7 (
+ AwEAAc9BtlREycexYH5az+dIbhI6sM56F+kd
+ SI43ZTGNT/Bam5vGrXju0uTHCJ2+KBwOSz7d
+ ZVchX0ulIJOUV9MteT0=
+ ) ; ZSK; alg = NSEC3RSASHA1 ; key id = 46349
+ 86400 DNSKEY 257 3 7 (
+ AwEAAcEFKHPyE1JMfRLhJK9mgcBZ+TR0Pj6u
+ shF6YbLkQoRs6Uzm458ErCcAdukJsTckqCzq
+ PFEqGLRztzyAJ7Z3G0k=
+ ) ; KSK; alg = NSEC3RSASHA1 ; key id = 18213
+ 86400 RRSIG DNSKEY 7 0 86400 (
+ 20500101000000 20190822124702 18213 .
+ ih4ScNqt9muT/Dqc05oO5T/xAyRK1/LblHph
+ GHedPHW7mC6IzsDBbqjD/P1nVK5RkM2Q+ozV
+ Ltbtmt2CafXsLA== )
+ 86400 RRSIG DNSKEY 7 0 86400 (
+ 20500101000000 20190822124702 46349 .
+ Lqle63cGoJdZA4CHHUq3ZqFxsbYATelzj5Dl
+ lDcc7vLbn2Qy9AVUC5I6UdZqMK2UDyO+DWCG
+ Cmq5705eAQlcsQ== )
+a.b.subtree1. 86400 IN AAAA 2001:db8::
+ 86400 RRSIG AAAA 7 3 86400 (
+ 20500101000000 20190822124702 46349 .
+ Hq6CUu3CVN/90b1Sozv0uIgH5ePxY3olc3eq
+ PoeyfdS+3HjSgb+Ji+GjYAAOMaVDS0APwwMe
+ pHxhdgO/zpKHRQ== )
+ 86400 NSEC . AAAA RRSIG NSEC
+ 86400 RRSIG NSEC 7 3 86400 (
+ 20500101000000 20190822124702 46349 .
+ lcJ7xdzxgTTvj2JiwzhDRyxTx2ZJ5zwzx0hC
+ ttTrSfG+2GyMnPzJ9MFid5S2w0WbWOWWLaKH
+ O0ucI8xvYInNAA== )
diff --git a/modules/priming/.packaging/test.config b/modules/priming/.packaging/test.config
new file mode 100644
index 0000000..63239f0
--- /dev/null
+++ b/modules/priming/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('priming')
+assert(priming)
+quit()
diff --git a/modules/priming/README.rst b/modules/priming/README.rst
new file mode 100644
index 0000000..b74bad4
--- /dev/null
+++ b/modules/priming/README.rst
@@ -0,0 +1,18 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-priming:
+
+Priming module
+==============
+
+The module for Initializing a DNS Resolver with Priming Queries implemented
+according to :rfc:`8109`. Purpose of the module is to keep up-to-date list of
+root DNS servers and associated IP addresses.
+
+Result of successful priming query replaces root hints distributed with
+the resolver software. Unlike other DNS resolvers, Knot Resolver caches
+result of priming query on disk and keeps the data between restarts until
+TTL expires.
+
+This module is enabled by default; you may disable it by adding
+``modules.unload('priming')`` to your configuration.
diff --git a/modules/priming/priming.lua b/modules/priming/priming.lua
new file mode 100644
index 0000000..624a9df
--- /dev/null
+++ b/modules/priming/priming.lua
@@ -0,0 +1,130 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- Module interface
+local ffi = require('ffi')
+
+local priming = {}
+priming.retry_time = 10 * sec -- retry time when priming fail
+
+-- internal state variables and functions
+local internal = {}
+internal.nsset = {} -- set of resolved nameservers
+internal.min_ttl = 0 -- minimal TTL of NS records
+internal.to_resolve = 0 -- number of pending queries to A or AAAA
+internal.prime = {} -- function triggering priming query
+internal.event = nil -- stores event id
+
+-- Copy hints from nsset table to resolver engine
+-- These addresses replace root hints loaded by default from file.
+-- They are stored outside cache and cache flush will not affect them.
+local function publish_hints(nsset)
+ local roothints = kres.context().root_hints
+ -- reset zone cut and clear address list
+ ffi.C.kr_zonecut_set(roothints, kres.str2dname("."))
+ for dname, addrsets in pairs(nsset) do
+ for i = 0, addrsets:rdcount() - 1 do
+ local rdpt = addrsets:rdata_pt(i)
+ ffi.C.kr_zonecut_add(roothints, dname, rdpt.data, rdpt.len)
+ end
+ end
+end
+
+-- Count A and AAAA addresses in nsset
+local function count_addresses(nsset)
+ local count = 0
+ for _, addrset in pairs(nsset) do
+ count = count + addrset:rdcount()
+ end
+ return count
+end
+
+-- Callback for response from A or AAAA query for root nameservers
+-- address is added to table internal.nsset.
+-- When all response is processed internal.nsset is published in resolver engine
+-- luacheck: no unused args
+local function address_callback(pkt, req)
+ if pkt == nil or pkt:rcode() ~= kres.rcode.NOERROR then
+ pkt = req.qsource.packet
+ log_info(ffi.C.LOG_GRP_PRIMING, "cannot resolve address '%s', type: %d", kres.dname2str(pkt:qname()), pkt:qtype())
+ else
+ local section = pkt:rrsets(kres.section.ANSWER)
+ for i = 1, #section do
+ local rrset_new = section[i]
+ if rrset_new.type == kres.type.A or rrset_new.type == kres.type.AAAA then
+ local owner = rrset_new:owner()
+ local rrset_comb = internal.nsset[owner]
+ if rrset_comb == nil then
+ rrset_comb = kres.rrset(nil, rrset_new.type)
+ internal.nsset[owner] = rrset_comb
+ end
+ assert(ffi.istype(kres.rrset, rrset_new))
+ rrset_comb:merge_rdata(rrset_new)
+ end
+ end
+ end
+ internal.to_resolve = internal.to_resolve - 1
+ if internal.to_resolve == 0 then
+ if count_addresses(internal.nsset) == 0 then
+ log_info(ffi.C.LOG_GRP_PRIMING, "cannot resolve any root server address, \
+ next priming query in %d seconds", priming.retry_time / sec)
+ internal.event = event.after(priming.retry_time, internal.prime)
+ else
+ publish_hints(internal.nsset)
+ log_info(ffi.C.LOG_GRP_PRIMING, "triggered priming query, next in %d seconds", internal.min_ttl)
+ internal.event = event.after(internal.min_ttl * sec, internal.prime)
+ end
+ end
+end
+
+-- Callback for priming query ('.' NS)
+-- For every NS record creates two separate queries for A and AAAA.
+-- These new queries should be resolved from cache.
+-- luacheck: no unused args
+local function priming_callback(pkt, req)
+ if pkt == nil or pkt:rcode() ~= kres.rcode.NOERROR then
+ log_info(ffi.C.LOG_GRP_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
+ end
+ local section = pkt:rrsets(kres.section.ANSWER)
+ for i = 1, #section do
+ local rr = section[i]
+ if rr.type == kres.type.NS then
+ internal.min_ttl = math.min(internal.min_ttl, rr:ttl())
+ internal.to_resolve = internal.to_resolve + 2 * rr.rrs.count
+ for k = 0, rr.rrs.count-1 do
+ local nsname_text = rr:tostring(k)
+ if net.ipv4 then
+ resolve(nsname_text, kres.type.A, kres.class.IN, 0, address_callback)
+ end
+ if net.ipv6 then
+ resolve(nsname_text, kres.type.AAAA, kres.class.IN, 0, address_callback)
+ end
+ end
+ end
+ end
+end
+
+-- trigger priming query
+function internal.prime()
+ internal.min_ttl = math.max(1, cache.max_ttl()) -- sanity check for disabled cache
+ internal.nsset = {}
+ internal.to_resolve = 0
+ resolve(".", kres.type.NS, kres.class.IN, 0, priming_callback)
+end
+
+function priming.init()
+ if internal.event then
+ error("Priming module is already loaded.")
+ else
+ internal.event = event.after(0 , internal.prime)
+ end
+end
+
+function priming.deinit()
+ if internal.event then
+ event.cancel(internal.event)
+ internal.event = nil
+ end
+end
+
+return priming
diff --git a/modules/rebinding/.packaging/test.config b/modules/rebinding/.packaging/test.config
new file mode 100644
index 0000000..0a84b88
--- /dev/null
+++ b/modules/rebinding/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('rebinding')
+assert(rebinding)
+quit()
diff --git a/modules/rebinding/README.rst b/modules/rebinding/README.rst
new file mode 100644
index 0000000..222d1da
--- /dev/null
+++ b/modules/rebinding/README.rst
@@ -0,0 +1,29 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-rebinding:
+
+Rebinding protection
+====================
+
+This module provides protection from `DNS Rebinding attack`_ by blocking
+answers which contain IPv4_ or IPv6_ addresses for private use
+(or some other special-use addresses).
+
+To enable this module insert following line into your configuration file:
+
+.. code-block:: lua
+
+ modules.load('rebinding < iterate')
+
+Please note that this module does not offer stable configuration interface
+yet. For this reason it is suitable mainly for public resolver operators
+who do not need to whitelist certain subnets.
+
+.. warning:: DNS Blacklists (`RFC 5782`_) often use `127.0.0.0/8` to blacklist
+ a domain. Using the rebinding module prevents DNSBL from functioning
+ properly.
+
+.. _`DNS Rebinding attack`: https://en.wikipedia.org/wiki/DNS_rebinding
+.. _IPv4: https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
+.. _IPv6: https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
+.. _`RFC 5782`: https://tools.ietf.org/html/rfc5782#section-2.1
diff --git a/modules/rebinding/rebinding.lua b/modules/rebinding/rebinding.lua
new file mode 100644
index 0000000..d5d6e74
--- /dev/null
+++ b/modules/rebinding/rebinding.lua
@@ -0,0 +1,115 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local ffi = require('ffi')
+
+-- Protection from DNS rebinding attacks
+local kres = require('kres')
+local renumber = require('kres_modules.renumber')
+local policy = require('kres_modules.policy')
+
+local M = {}
+M.layer = {}
+M.blacklist = {
+ -- https://www.iana.org/assignments/iana-ipv4-special-registry
+ -- + IPv4-to-IPv6 mapping
+ renumber.prefix('0.0.0.0/8', '0.0.0.0'),
+ renumber.prefix('::ffff:0.0.0.0/104', '::'),
+ renumber.prefix('10.0.0.0/8', '0.0.0.0'),
+ renumber.prefix('::ffff:10.0.0.0/104', '::'),
+ renumber.prefix('100.64.0.0/10', '0.0.0.0'),
+ renumber.prefix('::ffff:100.64.0.0/106', '::'),
+ renumber.prefix('127.0.0.0/8', '0.0.0.0'),
+ renumber.prefix('::ffff:127.0.0.0/104', '::'),
+ renumber.prefix('169.254.0.0/16', '0.0.0.0'),
+ renumber.prefix('::ffff:169.254.0.0/112', '::'),
+ renumber.prefix('172.16.0.0/12', '0.0.0.0'),
+ renumber.prefix('::ffff:172.16.0.0/108', '::'),
+ renumber.prefix('192.168.0.0/16', '0.0.0.0'),
+ renumber.prefix('::ffff:192.168.0.0/112', '::'),
+ -- https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
+ renumber.prefix('::/128', '::'),
+ renumber.prefix('::1/128', '::'),
+ renumber.prefix('fc00::/7', '::'),
+ renumber.prefix('fe80::/10', '::'),
+} -- second parameter for renumber module is ignored except for being v4 or v6
+
+local function is_rr_blacklisted(rr)
+ for i = 1, #M.blacklist do
+ local prefix = M.blacklist[i]
+ -- Match record type to address family and record address to given subnet
+ if renumber.match_subnet(prefix[1], prefix[2], prefix[4], rr) then
+ return true
+ end
+ end
+ return false
+end
+
+local function check_section(pkt, section)
+ local records = pkt:section(section)
+ local count = #records
+ if count == 0 then
+ return nil end
+ for i = 1, count do
+ local rr = records[i]
+ if rr.type == kres.type.A or rr.type == kres.type.AAAA then
+ local result = is_rr_blacklisted(rr)
+ if result then
+ return rr end
+ end
+ end
+end
+
+local function check_pkt(pkt)
+ for _, section in ipairs({kres.section.ANSWER,
+ kres.section.AUTHORITY,
+ kres.section.ADDITIONAL}) do
+ local bad_rr = check_section(pkt, section)
+ if bad_rr then
+ return bad_rr
+ end
+ end
+end
+
+local function refuse(req)
+ policy.REFUSE(nil, req)
+ 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
+function M.layer.consume(state, req, pkt)
+ if state == kres.FAIL then
+ return state end
+
+ local qry = req:current()
+ if qry.flags.CACHED or qry.flags.ALLOW_LOCAL then -- do not slow down cached queries
+ return state end
+
+ local bad_rr = check_pkt(pkt)
+ if not bad_rr then
+ return state end
+
+ qry.flags.RESOLVED = 1 -- stop iteration
+ qry.flags.CACHED = 1 -- do not cache
+
+ --[[ In case we're in a sub-query, we do not touch the final req answer.
+ Only this sub-query will get finished without a result - there we
+ rely on the iterator reacting to flags.RESOLVED
+ 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
+ state = refuse(req)
+ end
+ log_qry(qry, ffi.C.LOG_GRP_REBIND,
+ 'blocking blacklisted IP in RR \'%s\'\n', kres.rr2str(bad_rr))
+ return state
+end
+
+return M
diff --git a/modules/rebinding/test.integr/deckard.yaml b/modules/rebinding/test.integr/deckard.yaml
new file mode 100644
index 0000000..9b1793b
--- /dev/null
+++ b/modules/rebinding/test.integr/deckard.yaml
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+programs:
+- name: kresd
+ binary: kresd
+ additional:
+ - --noninteractive
+ templates:
+ - modules/rebinding/test.integr/kresd_config.j2
+ - tests/integration/hints_zone.j2
+ configs:
+ - config
+ - hints
diff --git a/modules/rebinding/test.integr/kresd_config.j2 b/modules/rebinding/test.integr/kresd_config.j2
new file mode 100644
index 0000000..aed3551
--- /dev/null
+++ b/modules/rebinding/test.integr/kresd_config.j2
@@ -0,0 +1,59 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+{% raw %}
+-- make sure DNSSEC is turned off for tests
+trust_anchors.remove('.')
+
+-- 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
+
+modules.load('rebinding < iterate')
+
+_hint_root_file('hints')
+cache.size = 2*MB
+log_level('debug')
+net.ipv6 = false
+{% endraw %}
+
+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(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 --git a/modules/rebinding/test.integr/module_rebinding.rpl b/modules/rebinding/test.integr/module_rebinding.rpl
new file mode 100644
index 0000000..1e344c5
--- /dev/null
+++ b/modules/rebinding/test.integr/module_rebinding.rpl
@@ -0,0 +1,834 @@
+; SPDX-License-Identifier: GPL-3.0-or-later
+; config options
+; target-fetch-policy: "0 0 0 0 0"
+; module-config: "iterator"
+; name: "."
+ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test protection from DNS rebinding
+
+; K.ROOT-SERVERS.NET.
+RANGE_BEGIN 0 1000
+ ADDRESS 193.0.14.129
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. IN NS K.ROOT-SERVERS.NET.
+SECTION ADDITIONAL
+K.ROOT-SERVERS.NET. IN A 193.0.14.129
+ENTRY_END
+
+; net.
+ENTRY_BEGIN
+MATCH opcode qname
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+net. IN NS
+SECTION AUTHORITY
+. IN SOA . . 0 0 0 0 0
+ENTRY_END
+
+; root-servers.net.
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+root-servers.net. IN NS
+SECTION ANSWER
+root-servers.net. IN NS k.root-servers.net.
+SECTION ADDITIONAL
+k.root-servers.net. IN A 193.0.14.129
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qname
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+root-servers.net. IN A
+SECTION AUTHORITY
+root-servers.net. IN SOA . . 0 0 0 0 0
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+k.root-servers.net. IN A
+SECTION ANSWER
+k.root-servers.net. IN A 193.0.14.129
+SECTION ADDITIONAL
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+k.root-servers.net. IN AAAA
+SECTION AUTHORITY
+root-servers.net. IN SOA . . 0 0 0 0 0
+ENTRY_END
+
+; gtld-servers.net.
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+gtld-servers.net. IN NS
+SECTION ANSWER
+gtld-servers.net. IN NS a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net. IN A 192.5.6.30
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qname
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+gtld-servers.net. IN A
+SECTION AUTHORITY
+gtld-servers.net. IN SOA . . 0 0 0 0 0
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+a.gtld-servers.net. IN A
+SECTION ANSWER
+a.gtld-servers.net. IN A 192.5.6.30
+SECTION ADDITIONAL
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+a.gtld-servers.net. IN AAAA
+SECTION AUTHORITY
+gtld-servers.net. IN SOA . . 0 0 0 0 0
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+com. IN A
+SECTION AUTHORITY
+com. IN NS a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net. IN A 192.5.6.30
+ENTRY_END
+RANGE_END
+
+; a.gtld-servers.net.
+RANGE_BEGIN 0 1000
+ ADDRESS 192.5.6.30
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+com. IN NS
+SECTION ANSWER
+com. IN NS a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net. IN A 192.5.6.30
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN A
+SECTION AUTHORITY
+example.com. IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
+ENTRY_END
+
+; NS with address pointing into a private range must not be followed
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+attacker.com. IN A
+SECTION AUTHORITY
+attacker.com. IN NS ns.attacker.com.
+SECTION ADDITIONAL
+ns.attacker.com. IN A 192.168.3.5
+ENTRY_END
+RANGE_END
+
+; ns.attacker.com.
+RANGE_BEGIN 0 1000
+ ADDRESS 19.168.3.5
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+attacker.com. IN NS
+SECTION ANSWER
+attacker.com. IN NS ns.attacker.com.
+SECTION ADDITIONAL
+ns.attacker.com. IN A 192.168.3.5
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.attacker.com. IN A
+SECTION ANSWER
+www.attacker.com. IN A 192.0.2.55
+ENTRY_END
+RANGE_END
+
+
+; ns.example.com.
+RANGE_BEGIN 0 1000
+ ADDRESS 1.2.3.4
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION ANSWER
+example.com. IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A 192.0.2.40
+ENTRY_END
+
+; blacklisted IP addresses
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+attack-ipv4-0-0-0-0.example.com. IN A
+SECTION ANSWER
+attack-ipv4-0-0-0-0.example.com. IN A 0.0.0.0
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+attack-ipv4over6-0-0-0-0.example.com. IN AAAA
+SECTION ANSWER
+attack-ipv4over6-0-0-0-0.example.com. IN AAAA ::ffff:0.0.0.0
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+attack-ipv4-10-1-2-3.example.com. IN A
+SECTION ANSWER
+attack-ipv4-10-1-2-3.example.com. IN A 10.1.2.3
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+attack-ipv4over6-10-2-3-4.example.com. IN AAAA
+SECTION ANSWER
+attack-ipv4over6-10-2-3-4.example.com. IN AAAA ::ffff:10.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+attack-ipv4-100-127-255-254.example.com. IN A
+SECTION ANSWER
+attack-ipv4-100-127-255-254.example.com. IN A 100.127.255.254
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+attack-ipv4over6-100-127-255-255.example.com. IN AAAA
+SECTION ANSWER
+attack-ipv4over6-100-127-255-255.example.com. IN AAAA ::ffff:100.127.255.255
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+attack-ipv4-127-0-0-1.example.com. IN A
+SECTION ANSWER
+attack-ipv4-127-0-0-1.example.com. IN A 127.0.0.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+attack-ipv4over6-127-0-0-1.example.com. IN AAAA
+SECTION ANSWER
+attack-ipv4over6-127-0-0-1.example.com. IN AAAA ::ffff:127.0.0.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+attack-ipv4-169-254-255-255.example.com. IN A
+SECTION ANSWER
+attack-ipv4-169-254-255-255.example.com. IN A 169.254.255.255
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+attack-ipv4over6-169-254-0-0.example.com. IN AAAA
+SECTION ANSWER
+attack-ipv4over6-169-254-0-0.example.com. IN AAAA ::ffff:169.254.0.0
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+attack-ipv4-172-16-0-0.example.com. IN A
+SECTION ANSWER
+attack-ipv4-172-16-0-0.example.com. IN A 172.16.0.0
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+attack-ipv4over6-172-31-255-255.example.com. IN AAAA
+SECTION ANSWER
+attack-ipv4over6-172-31-255-255.example.com. IN AAAA ::ffff:172.31.255.255
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+attack-ipv4-192-168-3-8.example.com. IN A
+SECTION ANSWER
+attack-ipv4-192-168-3-8.example.com. IN A 192.168.3.8
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+attack-ipv4over6-192-168-254-210.example.com. IN AAAA
+SECTION ANSWER
+attack-ipv4over6-192-168-254-210.example.com. IN AAAA ::ffff:192.168.254.210
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+attack-ipv6-.example.com. IN AAAA
+SECTION ANSWER
+attack-ipv6-.example.com. IN AAAA ::
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+attack-ipv6-1.example.com. IN AAAA
+SECTION ANSWER
+attack-ipv6-1.example.com. IN AAAA ::1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+attack-ipv6-fc00.example.com. IN AAAA
+SECTION ANSWER
+attack-ipv6-fc00.example.com. IN AAAA fc00::
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+attack-ipv6-fe80.example.com. IN AAAA
+SECTION ANSWER
+attack-ipv6-fe80.example.com. IN AAAA fe80::
+ENTRY_END
+
+RANGE_END
+
+STEP 11 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+
+; recursion happens here, no blacklisted IP address is present
+STEP 12 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A 192.0.2.40
+;SECTION AUTHORITY
+;example.com. IN NS ns.example.com.
+;SECTION ADDITIONAL
+;ns.example.com. IN A 1.2.3.4
+ENTRY_END
+
+; test that 0.0.0.0 is blacklisted
+STEP 201 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+attack-ipv4-0-0-0-0.example.com. IN A
+ENTRY_END
+
+STEP 202 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer authority
+REPLY QR RD RA REFUSED
+SECTION QUESTION
+attack-ipv4-0-0-0-0.example.com. IN A
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+explanation.invalid. TXT "blocked by DNS rebinding protection"
+ENTRY_END
+
+; test that ::ffff:0.0.0.0 is blacklisted
+STEP 211 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+attack-ipv4over6-0-0-0-0.example.com. IN AAAA
+ENTRY_END
+
+STEP 212 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer authority
+REPLY QR RD RA REFUSED
+SECTION QUESTION
+attack-ipv4over6-0-0-0-0.example.com. IN AAAA
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+explanation.invalid. TXT "blocked by DNS rebinding protection"
+ENTRY_END
+
+; test that 10.1.2.3 is blacklisted
+STEP 221 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+attack-ipv4-10-1-2-3.example.com. IN A
+ENTRY_END
+
+STEP 222 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer authority
+REPLY QR RD RA REFUSED
+SECTION QUESTION
+attack-ipv4-10-1-2-3.example.com. IN A
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+explanation.invalid. TXT "blocked by DNS rebinding protection"
+ENTRY_END
+
+; test that ::ffff:10.2.3.4 is blacklisted
+STEP 231 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+attack-ipv4over6-10-2-3-4.example.com. IN AAAA
+ENTRY_END
+
+STEP 232 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer authority
+REPLY QR RD RA REFUSED
+SECTION QUESTION
+attack-ipv4over6-10-2-3-4.example.com. IN AAAA
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+explanation.invalid. TXT "blocked by DNS rebinding protection"
+ENTRY_END
+
+; test that 100.127.255.254 is blacklisted
+STEP 241 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+attack-ipv4-100-127-255-254.example.com. IN A
+ENTRY_END
+
+STEP 242 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer authority
+REPLY QR RD RA REFUSED
+SECTION QUESTION
+attack-ipv4-100-127-255-254.example.com. IN A
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+explanation.invalid. TXT "blocked by DNS rebinding protection"
+ENTRY_END
+
+; test that ::ffff:100.127.255.255 is blacklisted
+STEP 251 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+attack-ipv4over6-100-127-255-255.example.com. IN AAAA
+ENTRY_END
+
+STEP 252 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer authority
+REPLY QR RD RA REFUSED
+SECTION QUESTION
+attack-ipv4over6-100-127-255-255.example.com. IN AAAA
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+explanation.invalid. TXT "blocked by DNS rebinding protection"
+ENTRY_END
+
+; test that 127.0.0.1 is blacklisted
+STEP 261 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+attack-ipv4-127-0-0-1.example.com. IN A
+ENTRY_END
+
+STEP 262 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer authority
+REPLY QR RD RA REFUSED
+SECTION QUESTION
+attack-ipv4-127-0-0-1.example.com. IN A
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+explanation.invalid. TXT "blocked by DNS rebinding protection"
+ENTRY_END
+
+; test that ::ffff:127.0.0.1 is blacklisted
+STEP 271 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+attack-ipv4over6-127-0-0-1.example.com. IN AAAA
+ENTRY_END
+
+STEP 272 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer authority
+REPLY QR RD RA REFUSED
+SECTION QUESTION
+attack-ipv4over6-127-0-0-1.example.com. IN AAAA
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+explanation.invalid. TXT "blocked by DNS rebinding protection"
+ENTRY_END
+
+; test that 169.254.255.255 is blacklisted
+STEP 281 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+attack-ipv4-169-254-255-255.example.com. IN A
+ENTRY_END
+
+STEP 282 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer authority
+REPLY QR RD RA REFUSED
+SECTION QUESTION
+attack-ipv4-169-254-255-255.example.com. IN A
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+explanation.invalid. TXT "blocked by DNS rebinding protection"
+ENTRY_END
+
+; test that ::ffff:169.254.0.0 is blacklisted
+STEP 291 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+attack-ipv4over6-169-254-0-0.example.com. IN AAAA
+ENTRY_END
+
+STEP 292 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer authority
+REPLY QR RD RA REFUSED
+SECTION QUESTION
+attack-ipv4over6-169-254-0-0.example.com. IN AAAA
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+explanation.invalid. TXT "blocked by DNS rebinding protection"
+ENTRY_END
+
+; test that 172.16.0.0 is blacklisted
+STEP 301 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+attack-ipv4-172-16-0-0.example.com. IN A
+ENTRY_END
+
+STEP 302 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer authority
+REPLY QR RD RA REFUSED
+SECTION QUESTION
+attack-ipv4-172-16-0-0.example.com. IN A
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+explanation.invalid. TXT "blocked by DNS rebinding protection"
+ENTRY_END
+
+; test that ::ffff:172.31.255.255 is blacklisted
+STEP 311 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+attack-ipv4over6-172-31-255-255.example.com. IN AAAA
+ENTRY_END
+
+STEP 312 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer authority
+REPLY QR RD RA REFUSED
+SECTION QUESTION
+attack-ipv4over6-172-31-255-255.example.com. IN AAAA
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+explanation.invalid. TXT "blocked by DNS rebinding protection"
+ENTRY_END
+
+; test that 192.168.3.8 is blacklisted
+STEP 321 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+attack-ipv4-192-168-3-8.example.com. IN A
+ENTRY_END
+
+STEP 322 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer authority
+REPLY QR RD RA REFUSED
+SECTION QUESTION
+attack-ipv4-192-168-3-8.example.com. IN A
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+explanation.invalid. TXT "blocked by DNS rebinding protection"
+ENTRY_END
+
+; test that ::ffff:192.168.254.210 is blacklisted
+STEP 331 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+attack-ipv4over6-192-168-254-210.example.com. IN AAAA
+ENTRY_END
+
+STEP 332 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer authority
+REPLY QR RD RA REFUSED
+SECTION QUESTION
+attack-ipv4over6-192-168-254-210.example.com. IN AAAA
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+explanation.invalid. TXT "blocked by DNS rebinding protection"
+ENTRY_END
+
+; test that :: is blacklisted
+STEP 341 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+attack-ipv6-.example.com. IN AAAA
+ENTRY_END
+
+STEP 342 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer authority
+REPLY QR RD RA REFUSED
+SECTION QUESTION
+attack-ipv6-.example.com. IN AAAA
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+explanation.invalid. TXT "blocked by DNS rebinding protection"
+ENTRY_END
+
+; test that ::1 is blacklisted
+STEP 351 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+attack-ipv6-1.example.com. IN AAAA
+ENTRY_END
+
+STEP 352 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer authority
+REPLY QR RD RA REFUSED
+SECTION QUESTION
+attack-ipv6-1.example.com. IN AAAA
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+explanation.invalid. TXT "blocked by DNS rebinding protection"
+ENTRY_END
+
+; test that fc00:: is blacklisted
+STEP 361 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+attack-ipv6-fc00.example.com. IN AAAA
+ENTRY_END
+
+STEP 362 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer authority
+REPLY QR RD RA REFUSED
+SECTION QUESTION
+attack-ipv6-fc00.example.com. IN AAAA
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+explanation.invalid. TXT "blocked by DNS rebinding protection"
+ENTRY_END
+
+; test that fe80:: is blacklisted
+STEP 371 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+attack-ipv6-fe80.example.com. IN AAAA
+ENTRY_END
+
+STEP 372 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer authority
+REPLY QR RD RA REFUSED
+SECTION QUESTION
+attack-ipv6-fe80.example.com. IN AAAA
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+explanation.invalid. TXT "blocked by DNS rebinding protection"
+ENTRY_END
+
+STEP 401 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+
+; it still works if no blacklisted IP address is present
+STEP 402 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A 192.0.2.40
+ENTRY_END
+
+STEP 501 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.attacker.com. IN A
+ENTRY_END
+
+; NS for attacker.com. has IP address from private range, it must fail
+STEP 502 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer authority
+REPLY QR RD RA REFUSED
+SECTION QUESTION
+www.attacker.com. IN A
+SECTION ANSWER
+SECTION AUTHORITY
+SECTION ADDITIONAL
+explanation.invalid. TXT "blocked by DNS rebinding protection"
+ENTRY_END
+
+SCENARIO_END
diff --git a/modules/refuse_nord/.packaging/test.config b/modules/refuse_nord/.packaging/test.config
new file mode 100644
index 0000000..8679e26
--- /dev/null
+++ b/modules/refuse_nord/.packaging/test.config
@@ -0,0 +1,3 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+assert(modules.load('refuse_nord') == true)
+quit()
diff --git a/modules/refuse_nord/README.rst b/modules/refuse_nord/README.rst
new file mode 100644
index 0000000..c093518
--- /dev/null
+++ b/modules/refuse_nord/README.rst
@@ -0,0 +1,16 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-refuse_nord:
+
+Refuse queries without RD bit
+=============================
+
+This module ensures all queries without RD (recursion desired) bit set in query
+are answered with REFUSED. This prevents snooping on the resolver's cache content.
+
+The module is loaded by default. If you'd like to disable this behavior, you can
+unload it:
+
+.. code-block:: lua
+
+ modules.unload('refuse_nord')
diff --git a/modules/refuse_nord/meson.build b/modules/refuse_nord/meson.build
new file mode 100644
index 0000000..1bcdbd7
--- /dev/null
+++ b/modules/refuse_nord/meson.build
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+# C module: refuse_nord
+
+integr_tests += [
+ ['refuse_nord', meson.current_source_dir() / 'test.integr'],
+]
+
+refuse_nord_src = files([
+ 'refuse_nord.c',
+])
+c_src_lint += refuse_nord_src
+
+refuse_nord_mod = shared_module(
+ 'refuse_nord',
+ refuse_nord_src,
+ dependencies: libknot,
+ include_directories: mod_inc_dir,
+ name_prefix: '',
+ install: true,
+ install_dir: modules_dir,
+)
diff --git a/modules/refuse_nord/refuse_nord.c b/modules/refuse_nord/refuse_nord.c
new file mode 100644
index 0000000..607ff61
--- /dev/null
+++ b/modules/refuse_nord/refuse_nord.c
@@ -0,0 +1,38 @@
+/* Copyright (C) Knot Resolver contributors.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * This module responds to all queries without RD bit set with REFUSED. */
+
+#include <libknot/consts.h>
+#include <libknot/packet/pkt.h>
+#include "daemon/worker.h"
+#include "lib/module.h"
+#include "lib/layer.h"
+
+static int refuse_nord_query(kr_layer_t *ctx)
+{
+ struct kr_request *req = ctx->req;
+ uint8_t rd = knot_wire_get_rd(req->qsource.packet->wire);
+ if (rd)
+ return ctx->state;
+
+ 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);
+ kr_request_set_extended_error(req, KNOT_EDNS_EDE_NOTAUTH, "ABC4");
+ ctx->state = KR_STATE_DONE;
+ return ctx->state;
+}
+
+KR_EXPORT int refuse_nord_init(struct kr_module *module)
+{
+ static const kr_layer_api_t layer = {
+ .begin = &refuse_nord_query,
+ };
+ module->layer = &layer;
+ return kr_ok();
+}
+
+KR_MODULE_EXPORT(refuse_nord)
diff --git a/modules/refuse_nord/test.integr/deckard.yaml b/modules/refuse_nord/test.integr/deckard.yaml
new file mode 100644
index 0000000..60bf040
--- /dev/null
+++ b/modules/refuse_nord/test.integr/deckard.yaml
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+programs:
+- name: kresd
+ binary: kresd
+ additional:
+ - --noninteractive
+ templates:
+ - modules/refuse_nord/test.integr/kresd_config.j2
+ - tests/integration/hints_zone.j2
+ configs:
+ - config
+ - hints
diff --git a/modules/refuse_nord/test.integr/kresd_config.j2 b/modules/refuse_nord/test.integr/kresd_config.j2
new file mode 100644
index 0000000..1abe4e4
--- /dev/null
+++ b/modules/refuse_nord/test.integr/kresd_config.j2
@@ -0,0 +1,56 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+{% raw %}
+-- make sure DNSSEC is turned off for tests
+trust_anchors.remove('.')
+
+-- 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
+
+_hint_root_file('hints')
+cache.size = 2*MB
+log_level('debug')
+{% endraw %}
+
+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(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 --git a/modules/refuse_nord/test.integr/refuse_nord.rpl b/modules/refuse_nord/test.integr/refuse_nord.rpl
new file mode 100644
index 0000000..5515b3a
--- /dev/null
+++ b/modules/refuse_nord/test.integr/refuse_nord.rpl
@@ -0,0 +1,24 @@
+; SPDX-License-Identifier: GPL-3.0-or-later
+; config options
+ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test refuse queries without RD bit
+
+STEP 10 QUERY
+ENTRY_BEGIN
+; RD bit is cleared
+SECTION QUESTION
+www.example.com IN A
+ENTRY_END
+
+STEP 20 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer
+REPLY QR RA REFUSED
+SECTION QUESTION
+www.example.com IN A
+SECTION ANSWER
+ENTRY_END
+
+SCENARIO_END
diff --git a/modules/renumber/.packaging/test.config b/modules/renumber/.packaging/test.config
new file mode 100644
index 0000000..37f136a
--- /dev/null
+++ b/modules/renumber/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('renumber')
+assert(renumber)
+quit()
diff --git a/modules/renumber/README.rst b/modules/renumber/README.rst
new file mode 100644
index 0000000..2e68991
--- /dev/null
+++ b/modules/renumber/README.rst
@@ -0,0 +1,36 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-renumber:
+
+IP address renumbering
+======================
+
+The module renumbers addresses in answers to different address space.
+e.g. you can redirect malicious addresses to a blackhole, or use private address ranges
+in local zones, that will be remapped to real addresses by the resolver.
+
+
+.. warning:: While requests are still validated using DNSSEC, the signatures
+ are stripped from final answer. The reason is that the address synthesis
+ breaks signatures. You can see whether an answer was valid or not based on
+ the AD flag.
+
+Example configuration
+---------------------
+
+.. code-block:: lua
+
+ modules = {
+ renumber = {
+ -- Source subnet, destination subnet
+ {'10.10.10.0/24', '192.168.1.0'},
+ -- Remap /16 block to localhost address range
+ {'166.66.0.0/16', '127.0.0.0'},
+ -- Remap /26 subnet (64 ip addresses)
+ {'166.55.77.128/26', '127.0.0.192'},
+ -- Remap a /32 block to a single address
+ {'2001:db8::/32', '::1!'},
+ }
+ }
+
+.. TODO: renumber.name() hangs in vacuum, kind of. No occurrences in code or docs, and probably bad UX.
diff --git a/modules/renumber/renumber.lua b/modules/renumber/renumber.lua
new file mode 100644
index 0000000..60803d5
--- /dev/null
+++ b/modules/renumber/renumber.lua
@@ -0,0 +1,181 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- Module interface
+local ffi = require('ffi')
+local prefixes_global = {}
+
+-- get address from config: either subnet prefix or fixed endpoint
+local function extract_address(target)
+ local idx = string.find(target, "!", 1, true)
+ if idx == nil then
+ return target, false
+ end
+ if idx ~= #target then
+ error("[renumber] \"!\" symbol in target is only accepted at the end of address")
+ end
+ return string.sub(target, 1, idx - 1), true
+end
+
+-- Create bitmask from integer mask for single octet: 2 -> 11000000
+local function getOctetBitmask(intMask)
+ return bit.lshift(bit.rshift(255, 8 - intMask), 8 - intMask)
+end
+
+-- Merge ipNet with ipHost, using intMask
+local function mergeIps(ipNet, ipHost, intMask)
+ local octetMask
+ local result = ""
+
+ if #ipNet ~= #ipHost then
+ return nil
+ end
+
+ if intMask == nil then
+ return ipNet
+ end
+
+ for currentOctetNo = 1, #ipNet do
+ if intMask >= 8 then
+ result = result .. ipNet:sub(currentOctetNo,currentOctetNo)
+ elseif (intMask <= 0) then
+ result = result .. ipHost:sub(currentOctetNo,currentOctetNo)
+ else
+ octetMask = getOctetBitmask(intMask)
+ result = result .. string.char(bit.bor(
+ bit.band(string.byte(ipNet:sub(currentOctetNo,currentOctetNo)), octetMask),
+ bit.band(string.byte(ipHost:sub(currentOctetNo,currentOctetNo)), bit.bnot(octetMask))
+ ))
+ end
+ intMask = intMask - 8
+ end
+
+ return result
+end
+
+-- Create subnet prefix rule
+local function matchprefix(subnet, addr)
+ local is_exact
+ addr, is_exact = extract_address(addr)
+ local target = kres.str2ip(addr)
+ if target == nil then error('[renumber] invalid address: '..addr) end
+ local addrtype = string.find(addr, ':', 1, true) and kres.type.AAAA or kres.type.A
+ local subnet_cd = ffi.new('char[16]')
+ local bitlen = ffi.C.kr_straddr_subnet(subnet_cd, subnet)
+ if bitlen < 0 then error('[renumber] invalid subnet: '..subnet) end
+ return {subnet_cd, bitlen, target, addrtype, is_exact}
+end
+
+-- Create name match rule
+local function matchname(name, addr)
+ local is_exact
+ addr, is_exact = extract_address(addr) -- though matchname() always leads to replacing whole address
+ local target = kres.str2ip(addr)
+ if target == nil then error('[renumber] invalid address: '..addr) end
+ local owner = todname(name)
+ if not name then error('[renumber] invalid name: '..name) end
+ local addrtype = string.find(addr, ':', 1, true) and kres.type.AAAA or kres.type.A
+ return {owner, nil, target, addrtype, is_exact}
+end
+
+-- Add subnet prefix rewrite rule
+local function add_prefix(subnet, addr)
+ local prefix = matchprefix(subnet, addr)
+ table.insert(prefixes_global, prefix)
+end
+
+-- Match IP against given subnet or record owner
+local function match_subnet(subnet, bitlen, addrtype, rr)
+ local addr = rr.rdata
+ return addrtype == rr.type and
+ ((bitlen and (#addr >= bitlen / 8) and (ffi.C.kr_bitcmp(subnet, addr, bitlen) == 0)) or subnet == rr.owner)
+end
+
+-- Renumber address record
+local function renumber_record(tbl, rr)
+ for i = 1, #tbl do
+ local prefix = tbl[i]
+ local subnet = prefix[1]
+ local bitlen = prefix[2]
+ local target = prefix[3]
+ local addrtype = prefix[4]
+ local is_exact = prefix[5]
+
+ -- Match record type to address family and record address to given subnet
+ -- If provided, compare record owner to prefix name
+ if match_subnet(subnet, bitlen, addrtype, rr) then
+ if is_exact then
+ rr.rdata = target
+ else
+ local mergedHost = mergeIps(target, rr.rdata, bitlen)
+ if mergedHost ~= nil then rr.rdata = mergedHost end
+ end
+
+ return rr
+ end
+ end
+ return nil
+end
+
+-- Renumber addresses based on config
+local function rule(prefixes)
+ return function (state, req)
+ if state == kres.FAIL then return state end
+ local pkt = req.answer
+ -- Only successful answers
+ local records = pkt:section(kres.section.ANSWER)
+ local ancount = #records
+ if ancount == 0 then return state end
+ -- Find renumber candidates
+ local changed = false
+ for i = 1, ancount do
+ local rr = records[i]
+ if rr.type == kres.type.A or rr.type == kres.type.AAAA then
+ local new_rr = renumber_record(prefixes, rr)
+ if new_rr ~= nil then
+ records[i] = new_rr
+ changed = true
+ end
+ end
+ end
+ -- If not rewritten, chain action
+ if not changed then return state end
+ -- Replace section if renumbering
+ local qname = pkt:qname()
+ local qclass = pkt:qclass()
+ local qtype = pkt:qtype()
+ pkt:recycle()
+ pkt:question(qname, qclass, qtype)
+ for i = 1, ancount do
+ local rr = records[i]
+ -- Strip signatures as rewritten data cannot be validated
+ if rr.type ~= kres.type.RRSIG then
+ pkt:put(rr.owner, rr.ttl, rr.class, rr.type, rr.rdata)
+ end
+ end
+ req:set_extended_error(kres.extended_error.FORGED, "DUQR")
+ return state
+ end
+end
+
+-- Export module interface
+local M = {
+ prefix = matchprefix,
+ name = matchname,
+ rule = rule,
+ match_subnet = match_subnet,
+}
+
+-- Config
+function M.config (conf)
+ if conf == nil then return end
+ 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[i][2]) end
+end
+
+-- Layers
+M.layer = {
+ finish = rule(prefixes_global),
+}
+
+return M
diff --git a/modules/renumber/renumber.test.lua b/modules/renumber/renumber.test.lua
new file mode 100644
index 0000000..97d6a6f
--- /dev/null
+++ b/modules/renumber/renumber.test.lua
@@ -0,0 +1,103 @@
+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('a10-3plus4.test.',
+ kres.type.A, {
+ kres.str2ip('10.3.0.1'),
+ kres.str2ip('10.4.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('a167-81.test.',
+ kres.type.A, kres.str2ip('167.81.254.221')),
+ 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('mix of known and unknown IPv4 ranges is remapped correctly to exact address',
+ 'a10-3plus4.test.', kres.type.A, kres.rcode.NOERROR, {'10.3.0.1', '192.168.3.10'})
+ 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('known IPv4 range is remapped when matching a rule with netmask not on a byte boundary',
+ 'a167-81.test.', kres.type.A, kres.rcode.NOERROR, {'127.0.30.221'})
+
+ 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()
+
+log_level('debug')
+modules.load('renumber < cache')
+renumber.config({
+ -- Source subnet, destination subnet
+ {'10.2.0.0/24', '192.168.2.0'},
+ {'10.4.0.0/24', '192.168.3.10!'},
+ {'166.66.0.0/16', '127.0.0.0'},
+ {'167.81.255.0/19', '127.0.0.0'},
+ {'2001:db8:1::/48', '2001:db8:2::'},
+})
+
+return {
+ test_renumber,
+}
diff --git a/modules/rfc7706.rst b/modules/rfc7706.rst
new file mode 100644
index 0000000..aa9b62e
--- /dev/null
+++ b/modules/rfc7706.rst
@@ -0,0 +1,12 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+Root on loopback (RFC 7706)
+---------------------------
+Knot Resolver developers think that literal implementation of :rfc:`7706`
+("Decreasing Access Time to Root Servers by Running One on Loopback")
+is a bad idea so it is not implemented in the form envisioned by the RFC.
+
+You can get the very similar effect without its downsides by combining
+:ref:`mod-prefill` and :ref:`mod-serve_stale` modules with Aggressive Use
+of DNSSEC-Validated Cache (:rfc:`8198`) behavior which is enabled
+automatically together with DNSSEC validation.
diff --git a/modules/serve_stale/.packaging/test.config b/modules/serve_stale/.packaging/test.config
new file mode 100644
index 0000000..362c4ec
--- /dev/null
+++ b/modules/serve_stale/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('serve_stale')
+assert(serve_stale)
+quit()
diff --git a/modules/serve_stale/README.rst b/modules/serve_stale/README.rst
new file mode 100644
index 0000000..6d2230c
--- /dev/null
+++ b/modules/serve_stale/README.rst
@@ -0,0 +1,23 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-serve_stale:
+
+Serve stale
+===========
+
+Demo module that allows using timed-out records in case kresd is
+unable to contact upstream servers.
+
+By default it allows stale-ness by up to one day,
+after roughly four seconds trying to contact the servers.
+It's quite configurable/flexible; see the beginning of the module source for details.
+See also the RFC draft_ (not fully followed) and :any:`cache.ns_tout`.
+
+Running
+-------
+.. code-block:: lua
+
+ modules = { 'serve_stale < cache' }
+
+.. _draft: https://tools.ietf.org/html/draft-ietf-dnsop-serve-stale-00
+
diff --git a/modules/serve_stale/serve_stale.lua b/modules/serve_stale/serve_stale.lua
new file mode 100644
index 0000000..faf07fb
--- /dev/null
+++ b/modules/serve_stale/serve_stale.lua
@@ -0,0 +1,42 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local M = {} -- the module
+
+local ffi = require('ffi')
+
+-- Beware that the timeout is only considered at certain points in time;
+-- approximately at multiples of KR_CONN_RTT_MAX.
+M.timeout = 3*sec
+
+M.callback = ffi.cast("kr_stale_cb",
+ function (ttl) --, name, type, qry)
+ --log_debug(ffi.C.SRVSTALE, ' => called back with TTL: ' .. tostring(ttl))
+ if ttl + 3600 * 24 > 0 then -- at most one day stale
+ return 1
+ else
+ return -1
+ end
+ end)
+
+M.layer = {
+ produce = function (state, req)
+ local qry = req:current()
+ -- Don't do anything for priming, prefetching, etc.
+ -- TODO: not all cases detected ATM.
+ if qry.flags.NO_CACHE then return state end
+
+ local now = ffi.C.kr_now()
+ local deadline = qry.creation_time_mono + M.timeout
+ if now > deadline or qry.flags.NO_NS_FOUND then
+ log_debug(ffi.C.LOG_GRP_SRVSTALE, ' => no reachable NS, using stale data')
+ qry.stale_cb = M.callback
+ -- TODO: probably start the same request that doesn't stale-serve,
+ -- but first we need some detection of non-interactive / internal requests.
+ -- resolve(kres.dname2str(qry.sname), qry.stype, qry.sclass)
+ end
+
+ return state
+ end,
+}
+
+return M
+
diff --git a/modules/serve_stale/test.integr/deckard.yaml b/modules/serve_stale/test.integr/deckard.yaml
new file mode 100644
index 0000000..c191345
--- /dev/null
+++ b/modules/serve_stale/test.integr/deckard.yaml
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+programs:
+- name: kresd
+ binary: kresd
+ additional:
+ - --noninteractive
+ templates:
+ - modules/serve_stale/test.integr/kresd_config.j2
+ - tests/integration/hints_zone.j2
+ configs:
+ - config
+ - hints
diff --git a/modules/serve_stale/test.integr/kresd_config.j2 b/modules/serve_stale/test.integr/kresd_config.j2
new file mode 100644
index 0000000..8570833
--- /dev/null
+++ b/modules/serve_stale/test.integr/kresd_config.j2
@@ -0,0 +1,70 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+{% raw %}
+modules = { 'serve_stale < cache' }
+
+-- make sure DNSSEC is turned off for tests
+trust_anchors.remove('.')
+
+-- 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
+
+_hint_root_file('hints')
+cache.size = 2*MB
+log_level('debug')
+{% endraw %}
+
+{% if DO_IP6 == "true" %}
+net.ipv6 = true
+{% else %}
+net.ipv6 = false
+{% endif %}
+
+{% if DO_IP4 == "true" %}
+net.ipv4 = true
+{% else %}
+net.ipv4 = false
+{% endif %}
+
+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(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 --git a/modules/serve_stale/test.integr/module_serve_stale.rpl b/modules/serve_stale/test.integr/module_serve_stale.rpl
new file mode 100644
index 0000000..3ab3a84
--- /dev/null
+++ b/modules/serve_stale/test.integr/module_serve_stale.rpl
@@ -0,0 +1,280 @@
+do-ip6: no
+; SPDX-License-Identifier: GPL-3.0-or-later
+; config options
+ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Modified iter_resolve.rpl to test serve_stale module.
+
+; K.ROOT-SERVERS.NET.
+RANGE_BEGIN 0 100
+ ADDRESS 193.0.14.129
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. IN NS K.ROOT-SERVERS.NET.
+SECTION ADDITIONAL
+K.ROOT-SERVERS.NET. 30 IN A 193.0.14.129
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+com. IN A
+SECTION AUTHORITY
+com. IN NS a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net. 30 IN A 192.5.6.30
+ENTRY_END
+
+; net.
+ENTRY_BEGIN
+MATCH opcode qname
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+net. IN NS
+SECTION AUTHORITY
+. IN SOA . . 0 0 0 0 0
+ENTRY_END
+
+; root-servers.net.
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+root-servers.net. IN NS
+SECTION ANSWER
+root-servers.net. 30 IN NS k.root-servers.net.
+SECTION ADDITIONAL
+k.root-servers.net. 30 IN A 193.0.14.129
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+root-servers.net. IN A
+SECTION AUTHORITY
+root-servers.net. 30 IN SOA . . 0 0 0 0 0
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+k.root-servers.net. IN A
+SECTION ANSWER
+k.root-servers.net. 30 IN A 193.0.14.129
+SECTION ADDITIONAL
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+k.root-servers.net. IN AAAA
+SECTION AUTHORITY
+root-servers.net. 30 IN SOA . . 0 0 0 0 0
+ENTRY_END
+
+; gtld-servers.net.
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+gtld-servers.net. IN NS
+SECTION ANSWER
+gtld-servers.net. IN NS a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net. 30 IN A 192.5.6.30
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+gtld-servers.net. IN A
+SECTION AUTHORITY
+gtld-servers.net. 30 IN SOA . . 0 0 0 0 0
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+a.gtld-servers.net. IN A
+SECTION ANSWER
+a.gtld-servers.net. 30 IN A 192.5.6.30
+SECTION ADDITIONAL
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+a.gtld-servers.net. IN AAAA
+SECTION AUTHORITY
+gtld-servers.net. 30 IN SOA . . 0 0 0 0 0
+ENTRY_END
+RANGE_END
+
+; a.gtld-servers.net.
+RANGE_BEGIN 0 100
+ ADDRESS 192.5.6.30
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+com. IN NS
+SECTION ANSWER
+com. IN NS a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net. 30 IN A 192.5.6.30
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN A
+SECTION AUTHORITY
+example.com. IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. 30 IN A 1.2.3.4
+ENTRY_END
+RANGE_END
+
+; ns.example.com.
+RANGE_BEGIN 0 100
+ ADDRESS 1.2.3.4
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION ANSWER
+example.com. 30 IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. 30 IN A 1.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. 30 IN A 10.20.30.40
+SECTION AUTHORITY
+example.com. 30 IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. 30 IN A 1.2.3.4
+ENTRY_END
+RANGE_END
+
+; K.ROOT-SERVERS.NET.
+; return REFUSED to all queries
+RANGE_BEGIN 101 200
+ ADDRESS 193.0.14.129
+ ADDRESS 192.5.6.30
+ ADDRESS 1.2.3.4
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR REFUSED
+SECTION QUESTION
+. IN A
+SECTION AUTHORITY
+SECTION ADDITIONAL
+ENTRY_END
+RANGE_END
+
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+
+; recursion happens here.
+STEP 10 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A 10.20.30.40
+;SECTION AUTHORITY
+;example.com. IN NS ns.example.com.
+;SECTION ADDITIONAL
+;ns.example.com. IN A 1.2.3.4
+ENTRY_END
+
+STEP 101 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+
+; Must be resolved from cache
+STEP 110 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A 10.20.30.40
+;SECTION AUTHORITY
+;example.com. IN NS ns.example.com.
+;SECTION ADDITIONAL
+;ns.example.com. IN A 1.2.3.4
+ENTRY_END
+
+; Expire cached records (TTL is 30)
+STEP 120 TIME_PASSES ELAPSE 60
+
+STEP 121 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+
+; Must be resolved from expired cache by serve_stale module
+STEP 130 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A 10.20.30.40
+;SECTION AUTHORITY
+;example.com. IN NS ns.example.com.
+;SECTION ADDITIONAL
+;ns.example.com. IN A 1.2.3.4
+ENTRY_END
+
+SCENARIO_END
diff --git a/modules/stats/.packaging/test.config b/modules/stats/.packaging/test.config
new file mode 100644
index 0000000..fd25460
--- /dev/null
+++ b/modules/stats/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('stats')
+assert(stats)
+quit()
diff --git a/modules/stats/README.rst b/modules/stats/README.rst
new file mode 100644
index 0000000..7d423aa
--- /dev/null
+++ b/modules/stats/README.rst
@@ -0,0 +1,211 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-stats:
+
+Statistics collector
+====================
+
+Module ``stats`` gathers various counters from the query resolution
+and server internals, and offers them as a key-value storage.
+These metrics can be either exported to :ref:`mod-graphite`,
+exposed as :ref:`mod-http-prometheus`, or processed using user-provided script
+as described in chapter :ref:`async-events`.
+
+.. note:: Please remember that each Knot Resolver instance keeps its own
+ statistics, and instances can be started and stopped dynamically. This might
+ affect your data postprocessing procedures if you are using
+ :ref:`systemd-multiple-instances`.
+
+.. _mod-stats-list:
+
+Built-in statistics
+-------------------
+
+Built-in counters keep track of number of queries and answers matching specific criteria.
+
++-----------------------------------------------------------------+
+| **Global request counters** |
++------------------+----------------------------------------------+
+| request.total | total number of DNS requests |
+| | (including internal client requests) |
++------------------+----------------------------------------------+
+| request.internal | internal requests generated by Knot Resolver |
+| | (e.g. DNSSEC trust anchor updates) |
++------------------+----------------------------------------------+
+| request.udp | external requests received over plain UDP |
+| | (:rfc:`1035`) |
++------------------+----------------------------------------------+
+| request.tcp | external requests received over plain TCP |
+| | (:rfc:`1035`) |
++------------------+----------------------------------------------+
+| request.dot | external requests received over |
+| | DNS-over-TLS (:rfc:`7858`) |
++------------------+----------------------------------------------+
+| 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** |
++-----------------+----------------------------------+
+| answer.total | total number of answered queries |
++-----------------+----------------------------------+
+| answer.cached | queries answered from cache |
++-----------------+----------------------------------+
+
++-----------------+----------------------------------+
+| **Answers categorized by RCODE** |
++-----------------+----------------------------------+
+| answer.noerror | NOERROR answers |
++-----------------+----------------------------------+
+| answer.nodata | NOERROR, but empty answers |
++-----------------+----------------------------------+
+| answer.nxdomain | NXDOMAIN answers |
++-----------------+----------------------------------+
+| answer.servfail | SERVFAIL answers |
++-----------------+----------------------------------+
+
++-----------------+----------------------------------+
+| **Answer latency** |
++-----------------+----------------------------------+
+| answer.1ms | completed in 1ms |
++-----------------+----------------------------------+
+| answer.10ms | completed in 10ms |
++-----------------+----------------------------------+
+| answer.50ms | completed in 50ms |
++-----------------+----------------------------------+
+| answer.100ms | completed in 100ms |
++-----------------+----------------------------------+
+| answer.250ms | completed in 250ms |
++-----------------+----------------------------------+
+| answer.500ms | completed in 500ms |
++-----------------+----------------------------------+
+| answer.1000ms | completed in 1000ms |
++-----------------+----------------------------------+
+| answer.1500ms | completed in 1500ms |
++-----------------+----------------------------------+
+| answer.slow | completed in more than 1500ms |
++-----------------+----------------------------------+
+
++-----------------+----------------------------------+
+| **Answer flags** |
++-----------------+----------------------------------+
+| answer.aa | authoritative answer |
++-----------------+----------------------------------+
+| answer.tc | truncated answer |
++-----------------+----------------------------------+
+| answer.ra | recursion available |
++-----------------+----------------------------------+
+| answer.rd | recursion desired (in answer!) |
++-----------------+----------------------------------+
+| answer.ad | authentic data (DNSSEC) |
++-----------------+----------------------------------+
+| answer.cd | checking disabled (DNSSEC) |
++-----------------+----------------------------------+
+| answer.do | DNSSEC answer OK |
++-----------------+----------------------------------+
+| answer.edns0 | EDNS0 present |
++-----------------+----------------------------------+
+
++-----------------+----------------------------------+
+| **Query flags** |
++-----------------+----------------------------------+
+| query.edns | queries with EDNS present |
++-----------------+----------------------------------+
+| query.dnssec | queries with DNSSEC DO=1 |
++-----------------+----------------------------------+
+
+Example:
+
+.. code-block:: none
+
+ modules.load('stats')
+
+ -- Enumerate metrics
+ > stats.list()
+ [answer.cached] => 486178
+ [iterator.tcp] => 490
+ [answer.noerror] => 507367
+ [answer.total] => 618631
+ [iterator.udp] => 102408
+ [query.concurrent] => 149
+
+ -- Query metrics by prefix
+ > stats.list('iter')
+ [iterator.udp] => 105104
+ [iterator.tcp] => 490
+
+ -- Fetch most common queries
+ > stats.frequent()
+ [1] => {
+ [type] => 2
+ [count] => 4
+ [name] => cz.
+ }
+
+ -- Fetch most common queries (sorted by frequency)
+ > table.sort(stats.frequent(), function (a, b) return a.count > b.count end)
+
+ -- Show recently contacted authoritative servers
+ > stats.upstreams()
+ [2a01:618:404::1] => {
+ [1] => 26 -- RTT
+ }
+ [128.241.220.33] => {
+ [1] => 31 - RTT
+ }
+
+ -- Set custom metrics from modules
+ > stats['filter.match'] = 5
+ > stats['filter.match']
+ 5
+
+Module reference
+----------------
+
+.. function:: stats.get(key)
+
+ :param string key: i.e. ``"answer.total"``
+ :return: ``number``
+
+Return nominal value of given metric.
+
+.. function:: stats.set('key val')
+
+Set nominal value of given metric.
+
+Example:
+
+.. code-block:: lua
+
+ stats.set('answer.total 5')
+ -- or syntactic sugar
+ stats['answer.total'] = 5
+
+
+.. function:: stats.list([prefix])
+
+ :param string prefix: optional metric prefix, i.e. ``"answer"`` shows only metrics beginning with "answer"
+
+Outputs collected metrics as a JSON dictionary.
+
+.. function:: stats.upstreams()
+
+Outputs a list of recent upstreams and their RTT. It is sorted by time and stored in a ring buffer of
+a fixed size. This means it's not aggregated and readable by multiple consumers, but also that
+you may lose entries if you don't read quickly enough. The default ring size is 512 entries, and may be overridden on compile time by ``-DUPSTREAMS_COUNT=X``.
+
+.. function:: stats.frequent()
+
+Outputs list of most frequent iterative queries as a JSON array. The queries are sampled probabilistically,
+and include subrequests. The list maximum size is 5000 entries, make diffs if you want to track it over time.
+
+.. function:: stats.clear_frequent()
+
+Clear the list of most frequent iterative queries.
+
+.. include:: ../modules/graphite/README.rst
+.. include:: ../modules/http/prometheus.rst
diff --git a/modules/stats/meson.build b/modules/stats/meson.build
new file mode 100644
index 0000000..4f2d41e
--- /dev/null
+++ b/modules/stats/meson.build
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+# C module: stats
+
+stats_src = files([
+ 'stats.c',
+])
+c_src_lint += stats_src
+
+integr_tests += [
+ ['stats', meson.current_source_dir() / 'test.integr'],
+]
+
+
+stats_mod = shared_module(
+ 'stats',
+ stats_src,
+ dependencies: [
+ libknot,
+ luajit_inc,
+ ],
+ include_directories: mod_inc_dir,
+ name_prefix: '',
+ install: true,
+ install_dir: modules_dir,
+)
diff --git a/modules/stats/stats.c b/modules/stats/stats.c
new file mode 100644
index 0000000..ebb2877
--- /dev/null
+++ b/modules/stats/stats.c
@@ -0,0 +1,534 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/**
+ * @file stats.c
+ * @brief Storage for various counters and metrics from query resolution.
+ *
+ * You can either reuse this module to compute statistics or store custom metrics
+ * in it via the extensions.
+ */
+
+#include <libknot/packet/pkt.h>
+#include <libknot/packet/wire.h>
+#include <libknot/descriptor.h>
+#include <ccan/json/json.h>
+#include <contrib/cleanup.h>
+#include <arpa/inet.h>
+#include <lua.h>
+
+#include "lib/generic/trie.h"
+#include "lib/layer/iterate.h"
+#include "lib/rplan.h"
+#include "lib/module.h"
+#include "lib/layer.h"
+#include "lib/resolve.h"
+
+/* Defaults */
+#define VERBOSE_MSG(qry, ...) kr_log_q(qry, STATISTICS, __VA_ARGS__)
+#define FREQUENT_PSAMPLE 10 /* Sampling rate, 1 in N */
+#ifdef LRU_REP_SIZE
+ #define FREQUENT_COUNT LRU_REP_SIZE /* Size of frequent tables */
+#else
+ #define FREQUENT_COUNT 5000 /* Size of frequent tables */
+#endif
+#ifndef UPSTREAMS_COUNT
+ #define UPSTREAMS_COUNT 512 /* Size of recent upstreams */
+#endif
+
+/** @cond internal Fixed-size map of predefined metrics. */
+#define CONST_METRICS(X) \
+ X(answer,total) X(answer,noerror) X(answer,nodata) X(answer,nxdomain) X(answer,servfail) \
+ X(answer,cached) X(answer,1ms) X(answer,10ms) X(answer,50ms) X(answer,100ms) \
+ X(answer,250ms) X(answer,500ms) X(answer,1000ms) X(answer,1500ms) X(answer,slow) \
+ 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,xdp) \
+ X(request,dot) X(request,doh) X(request,internal) \
+ X(const,end)
+
+enum const_metric {
+ #define X(a,b) metric_ ## a ## _ ## b,
+ CONST_METRICS(X)
+ #undef X
+};
+struct const_metric_elm {
+ const char *key;
+ size_t val;
+};
+static struct const_metric_elm const_metrics[] = {
+ #define X(a,b) [metric_ ## a ## _ ## b] = { #a "." #b, 0 },
+ CONST_METRICS(X)
+ #undef X
+};
+/** @endcond */
+
+/** @internal LRU hash of most frequent names. */
+typedef lru_t(unsigned) namehash_t;
+typedef array_t(struct sockaddr_in6) addrlist_t;
+
+/** @internal Stats data structure. */
+struct stat_data {
+ trie_t *trie;
+ struct {
+ namehash_t *frequent;
+ } queries;
+ struct {
+ addrlist_t q;
+ size_t head;
+ } upstreams;
+};
+
+/** @internal We don't store/publish port, repurpose it for RTT instead. */
+#define sin6_rtt sin6_port
+
+/** @internal Add to const map counter */
+static inline void stat_const_add(struct stat_data *data, enum const_metric key, ssize_t incr)
+{
+ const_metrics[key].val += incr;
+}
+
+static int collect_answer(struct stat_data *data, knot_pkt_t *pkt)
+{
+ stat_const_add(data, metric_answer_total, 1);
+ /* Count per-rcode */
+ switch(knot_wire_get_rcode(pkt->wire)) {
+ case KNOT_RCODE_NOERROR:
+ if (knot_wire_get_ancount(pkt->wire) > 0)
+ stat_const_add(data, metric_answer_noerror, 1);
+ else
+ stat_const_add(data, metric_answer_nodata, 1);
+ break;
+ case KNOT_RCODE_NXDOMAIN: stat_const_add(data, metric_answer_nxdomain, 1); break;
+ case KNOT_RCODE_SERVFAIL: stat_const_add(data, metric_answer_servfail, 1); break;
+ default: break;
+ }
+
+ return kr_ok();
+}
+
+static inline int collect_key(char *key, const knot_dname_t *name, uint16_t type)
+{
+ memcpy(key, &type, sizeof(type));
+ int key_len = knot_dname_to_wire((uint8_t *)key + sizeof(type), name, KNOT_DNAME_MAXLEN);
+ if (key_len < 0) {
+ return kr_error(key_len);
+ }
+ return key_len + sizeof(type);
+}
+
+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];
+ for (size_t i = 0; i < rplan->resolved.len; ++i) {
+ /* Sample queries leading to iteration */
+ struct kr_query *qry = rplan->resolved.at[i];
+ if (qry->flags.CACHED) {
+ continue;
+ }
+ /* Consider 1 in N for frequent sampling.
+ * TODO: redesign the sampling approach. */
+ if (kr_rand_coin(1, FREQUENT_PSAMPLE)) {
+ int key_len = collect_key(key, qry->sname, qry->stype);
+ if (kr_fails_assert(key_len >= 0))
+ continue;
+ unsigned *count = lru_get_new(data->queries.frequent, key, key_len, NULL);
+ if (count)
+ *count += 1;
+ }
+ }
+}
+
+static int collect_rtt(kr_layer_t *ctx, knot_pkt_t *pkt)
+{
+ struct kr_request *req = ctx->req;
+ struct kr_query *qry = req->current_query;
+ if (qry->flags.CACHED || !req->upstream.transport) {
+ return ctx->state;
+ }
+
+ /* Push address and RTT to the ring buffer head */
+ struct kr_module *module = ctx->api->data;
+ struct stat_data *data = module->data;
+
+ /* Socket address is encoded into sockaddr_in6 struct that
+ * unions with sockaddr_in and differ in sa_family */
+ struct sockaddr_in6 *e = &data->upstreams.q.at[data->upstreams.head];
+ const union kr_sockaddr *src = &req->upstream.transport->address;
+ switch (src->ip.sa_family) {
+ case AF_INET: memcpy(e, &src->ip4, sizeof(src->ip4)); break;
+ case AF_INET6: memcpy(e, &src->ip6, sizeof(src->ip6)); break;
+ default: return ctx->state;
+ }
+ /* Replace port number with the RTT information (cap is UINT16_MAX milliseconds) */
+ e->sin6_rtt = req->upstream.rtt;
+
+ /* Advance ring buffer head */
+ data->upstreams.head = (data->upstreams.head + 1) % UPSTREAMS_COUNT;
+ return ctx->state;
+}
+
+static int collect_transport(kr_layer_t *ctx)
+{
+ struct kr_request *req = ctx->req;
+ struct kr_module *module = ctx->api->data;
+ struct stat_data *data = module->data;
+
+ stat_const_add(data, metric_request_total, 1);
+ if (req->qsource.dst_addr == NULL) {
+ stat_const_add(data, metric_request_internal, 1);
+ return ctx->state;
+ }
+
+ /**
+ * Count each transport only once,
+ * 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);
+ else if (req->qsource.flags.tls)
+ 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;
+}
+
+static int collect(kr_layer_t *ctx)
+{
+ struct kr_request *param = ctx->req;
+ struct kr_module *module = ctx->api->data;
+ struct kr_rplan *rplan = &param->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);
+ /* Count cached and unresolved */
+ if (rplan->resolved.len > 0) {
+ /* Histogram of answer latency. */
+ struct kr_query *first = rplan->resolved.at[0];
+ uint64_t elapsed = kr_now() - first->timestamp_mono;
+ if (elapsed <= 1) {
+ stat_const_add(data, metric_answer_1ms, 1);
+ } else if (elapsed <= 10) {
+ stat_const_add(data, metric_answer_10ms, 1);
+ } else if (elapsed <= 50) {
+ stat_const_add(data, metric_answer_50ms, 1);
+ } else if (elapsed <= 100) {
+ stat_const_add(data, metric_answer_100ms, 1);
+ } else if (elapsed <= 250) {
+ stat_const_add(data, metric_answer_250ms, 1);
+ } else if (elapsed <= 500) {
+ stat_const_add(data, metric_answer_500ms, 1);
+ } else if (elapsed <= 1000) {
+ stat_const_add(data, metric_answer_1000ms, 1);
+ } else if (elapsed <= 1500) {
+ stat_const_add(data, metric_answer_1500ms, 1);
+ } else {
+ stat_const_add(data, metric_answer_slow, 1);
+ }
+ /* Observe the final query. */
+ struct kr_query *last = kr_rplan_last(rplan);
+ stat_const_add(data, metric_answer_cached, last->flags.CACHED);
+ }
+
+ /* Keep stats of all response header flags;
+ * these don't return bool, so that's why we use !! */
+ stat_const_add(data, metric_answer_aa, !!knot_wire_get_aa(param->answer->wire));
+ stat_const_add(data, metric_answer_tc, !!knot_wire_get_tc(param->answer->wire));
+ stat_const_add(data, metric_answer_rd, !!knot_wire_get_rd(param->answer->wire));
+ stat_const_add(data, metric_answer_ra, !!knot_wire_get_ra(param->answer->wire));
+ stat_const_add(data, metric_answer_ad, !!knot_wire_get_ad(param->answer->wire));
+ stat_const_add(data, metric_answer_cd, !!knot_wire_get_cd(param->answer->wire));
+
+ /* EDNS0 stats */
+ stat_const_add(data, metric_answer_edns0, knot_pkt_has_edns(param->answer));
+ stat_const_add(data, metric_answer_do, knot_pkt_has_dnssec(param->answer));
+
+ /* Query parameters and transport mode */
+ /*
+ DEPRECATED
+ use new names metric_answer_edns0 and metric_answer_do
+ */
+ stat_const_add(data, metric_query_edns, knot_pkt_has_edns(param->answer));
+ stat_const_add(data, metric_query_dnssec, knot_pkt_has_dnssec(param->answer));
+
+ return ctx->state;
+}
+
+/**
+ * Set nominal value of a key.
+ *
+ * Input: { key, val }
+ *
+ */
+static char* stats_set(void *env, struct kr_module *module, const char *args)
+{
+ if (args == NULL)
+ return NULL;
+
+ struct stat_data *data = module->data;
+
+ auto_free char *pair = strdup(args);
+ char *val = strchr(pair, ' ');
+ if (val) {
+ *val = '\0';
+ size_t number = strtoul(val + 1, NULL, 10);
+ for (unsigned i = 0; i < metric_const_end; ++i) {
+ if (strcmp(const_metrics[i].key, pair) == 0) {
+ const_metrics[i].val = number;
+ return NULL;
+ }
+ }
+ trie_val_t *trie_val = trie_get_ins(data->trie, pair, strlen(pair));
+ *trie_val = (void *)number;
+ }
+
+ return NULL;
+}
+
+/**
+ * Retrieve metrics by key.
+ *
+ * Input: string key
+ * Output: number value
+ */
+static char* stats_get(void *env, struct kr_module *module, const char *args)
+{
+ if (args == NULL)
+ return NULL;
+
+ struct stat_data *data = module->data;
+
+ /* Expecting CHAR_BIT to be 8, this is a safe bet */
+ char *ret = malloc(3 * sizeof(size_t) + 2);
+ if (!ret) {
+ return NULL;
+ }
+
+ /* Check if it exists in const map. */
+ for (unsigned i = 0; i < metric_const_end; ++i) {
+ if (strcmp(const_metrics[i].key, args) == 0) {
+ sprintf(ret, "%zu", const_metrics[i].val);
+ return ret;
+ }
+ }
+ /* Check in variable map */
+ trie_val_t *val = trie_get_try(data->trie, args, strlen(args));
+ if (!val) {
+ free(ret);
+ return NULL;
+ }
+ sprintf(ret, "%zu", (size_t) *val);
+ return ret;
+}
+
+/** Checks whether:
+ * - `key` starts with `prefix`; OR
+ * - The prefix is a wildcard, which is indicated by `prefix_len` being zero. */
+static inline bool key_matches_prefix(const char *key, size_t key_len,
+ const char *prefix, size_t prefix_len)
+{
+ return prefix_len == 0 || (prefix_len <= key_len && memcmp(key, prefix, prefix_len) == 0);
+}
+
+struct list_entry_context {
+ JsonNode *root; /**< JSON object into which matching entries will be inserted. */
+ const char *key_prefix; /**< The prefix against which entries will be matched. */
+ size_t key_prefix_len; /**< Prefix length. Prefix is a wildcard if zero. */
+};
+
+/** Inserts the entry with a matching key into the JSON object. */
+static int list_entry(const char *key, uint32_t key_len, trie_val_t *val, void *baton)
+{
+ struct list_entry_context *ctx = baton;
+ if (!key_matches_prefix(key, key_len, ctx->key_prefix, ctx->key_prefix_len))
+ return 0;
+ size_t number = (size_t) *val;
+ auto_free char *key_nt = strndup(key, key_len);
+ json_append_member(ctx->root, key_nt, json_mknumber(number));
+ return 0;
+}
+
+/**
+ * List observed metrics.
+ *
+ * Output: { key: val, ... }
+ */
+static char* stats_list(void *env, struct kr_module *module, const char *args)
+{
+ JsonNode *root = json_mkobject();
+ /* Walk const metrics map */
+ size_t args_len = args ? strlen(args) : 0;
+ for (unsigned i = 0; i < metric_const_end; ++i) {
+ struct const_metric_elm *elm = &const_metrics[i];
+ if (!args || strncmp(elm->key, args, args_len) == 0) {
+ json_append_member(root, elm->key, json_mknumber(elm->val));
+ }
+ }
+ struct list_entry_context ctx = {
+ .root = root,
+ .key_prefix = args,
+ .key_prefix_len = args_len
+ };
+ struct stat_data *data = module->data;
+ trie_apply_with_key(data->trie, list_entry, &ctx);
+ char *ret = json_encode(root);
+ json_delete(root);
+ return ret;
+}
+
+/** @internal Helper for dump_list: add a single namehash_t item to JSON. */
+static enum lru_apply_do dump_value(const char *key, uint len, unsigned *val, void *baton)
+{
+ uint16_t key_type = 0;
+ /* Extract query name, type and counter */
+ memcpy(&key_type, key, sizeof(key_type));
+ KR_DNAME_GET_STR(key_name, (uint8_t *)key + sizeof(key_type));
+ KR_RRTYPE_GET_STR(type_str, key_type);
+
+ /* Convert to JSON object */
+ JsonNode *json_val = json_mkobject();
+ json_append_member(json_val, "count", json_mknumber(*val));
+ json_append_member(json_val, "name", json_mkstring(key_name));
+ json_append_member(json_val, "type", json_mkstring(type_str));
+ json_append_element((JsonNode *)baton, json_val);
+ return LRU_APPLY_DO_NOTHING; // keep the item
+}
+/**
+ * List frequent names.
+ *
+ * Output: [{ count: <counter>, name: <qname>, type: <qtype>}, ... ]
+ */
+static char* dump_list(void *env, struct kr_module *module, const char *args, namehash_t *table)
+{
+ if (!table) {
+ return NULL;
+ }
+ JsonNode *root = json_mkarray();
+ lru_apply(table, dump_value, root);
+ char *ret = json_encode(root);
+ json_delete(root);
+ return ret;
+}
+
+static char* dump_frequent(void *env, struct kr_module *module, const char *args)
+{
+ struct stat_data *data = module->data;
+ return dump_list(env, module, args, data->queries.frequent);
+}
+
+static char* clear_frequent(void *env, struct kr_module *module, const char *args)
+{
+ struct stat_data *data = module->data;
+ lru_reset(data->queries.frequent);
+ return NULL;
+}
+
+static char* dump_upstreams(void *env, struct kr_module *module, const char *args)
+{
+ struct stat_data *data = module->data;
+ if (!data) {
+ return NULL;
+ }
+
+ /* Walk the ring backwards until AF_UNSPEC or we hit head. */
+ JsonNode *root = json_mkobject();
+ size_t head = data->upstreams.head;
+ for (size_t i = 1; i < UPSTREAMS_COUNT; ++i) {
+ size_t h = (UPSTREAMS_COUNT + head - i) % UPSTREAMS_COUNT;
+ struct sockaddr_in6 *e = &data->upstreams.q.at[h];
+ if (e->sin6_family == AF_UNSPEC) {
+ break;
+ }
+ /* Convert address to string */
+ char addr_str[INET6_ADDRSTRLEN];
+ const char *ret = inet_ntop(e->sin6_family, kr_inaddr((const struct sockaddr *)e), addr_str, sizeof(addr_str));
+ if (!ret) {
+ break;
+ }
+ /* Append to map with an array encoding RTTs */
+ JsonNode *json_val = json_find_member(root, addr_str);
+ if (!json_val) {
+ json_val = json_mkarray();
+ json_append_member(root, addr_str, json_val);
+ }
+ json_append_element(json_val, json_mknumber(e->sin6_rtt));
+ }
+
+ /* Encode and return */
+ char *ret = json_encode(root);
+ json_delete(root);
+ return ret;
+}
+
+KR_EXPORT
+int stats_init(struct kr_module *module)
+{
+ static kr_layer_api_t layer = {
+ .consume = &collect_rtt,
+ .finish = &collect,
+ .begin = &collect_transport,
+ };
+ /* Store module reference */
+ layer.data = module;
+ module->layer = &layer;
+
+ static const struct kr_prop props[] = {
+ { &stats_set, "set", "Set {key, val} metrics.", },
+ { &stats_get, "get", "Get metrics for given key.", },
+ { &stats_list, "list", "List observed metrics.", },
+ { &dump_frequent, "frequent", "List most frequent queries.", },
+ { &clear_frequent,"clear_frequent", "Clear frequent queries log.", },
+ { &dump_upstreams, "upstreams", "List recently seen authoritatives.", },
+ { NULL, NULL, NULL }
+ };
+ module->props = props;
+
+ struct stat_data *data = calloc(1, sizeof(*data));
+ if (!data) {
+ return kr_error(ENOMEM);
+ }
+ data->trie = trie_create(NULL);
+ module->data = data;
+ lru_create(&data->queries.frequent, FREQUENT_COUNT, NULL, NULL);
+ /* Initialize ring buffer of recently visited upstreams */
+ array_init(data->upstreams.q);
+ 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) {
+ data->upstreams.q.at[i].sin6_family = AF_UNSPEC;
+ }
+ return kr_ok();
+}
+
+KR_EXPORT
+int stats_deinit(struct kr_module *module)
+{
+ struct stat_data *data = module->data;
+ if (data) {
+ trie_free(data->trie);
+ lru_free(data->queries.frequent);
+ array_clear(data->upstreams.q);
+ free(data);
+ }
+ return kr_ok();
+}
+
+KR_MODULE_EXPORT(stats)
+
+#undef VERBOSE_MSG
diff --git a/modules/stats/test.integr/deckard.yaml b/modules/stats/test.integr/deckard.yaml
new file mode 100644
index 0000000..6dd0c22
--- /dev/null
+++ b/modules/stats/test.integr/deckard.yaml
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+programs:
+- name: kresd
+ binary: kresd
+ additional:
+ - --noninteractive
+ templates:
+ - modules/stats/test.integr/kresd_config.j2
+ - tests/integration/hints_zone.j2
+ configs:
+ - config
+ - hints
diff --git a/modules/stats/test.integr/kresd_config.j2 b/modules/stats/test.integr/kresd_config.j2
new file mode 100644
index 0000000..4db7caa
--- /dev/null
+++ b/modules/stats/test.integr/kresd_config.j2
@@ -0,0 +1,114 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local ffi = require('ffi')
+
+{% raw %}
+modules.load('stats')
+
+FWD_TARGET = policy.FORWARD('192.0.2.1')
+
+function check_stats(got)
+ log_info(ffi.C.LOG_GRP_TESTS, 'checking if stat values match expected values:')
+ local expected = {
+ ['answer.cd'] = 2,
+ ['answer.cached'] = 1,
+ ['answer.nodata'] = 1,
+ ['answer.noerror'] = 2,
+ ['answer.nxdomain'] = 1,
+ ['answer.servfail'] = 2,
+ ['answer.edns0'] = 6,
+ ['answer.ra'] = 6,
+ ['answer.rd'] = 5,
+ ['answer.do'] = 1,
+ ['answer.ad'] = 0,
+ ['answer.tc'] = 0,
+ ['answer.aa'] = 0,
+ ['answer.total'] = 6
+ }
+ print(table_print(expected))
+
+ local ok = true
+ for key, expval in pairs(expected) do
+ if got[key] ~= expval then
+ log_info(ffi.C.LOG_GRP_TESTS,
+ 'ERROR: stats key ' .. key
+ .. ' has unexpected value'
+ .. ' (expected ' .. tostring(expval)
+ .. ' got ' .. tostring(got[key] .. ')'))
+ ok = false
+ end
+ end
+ if ok then
+ log_info(ffi.C.LOG_GRP_TESTS, 'no problem found')
+ return FWD_TARGET
+ else
+ return policy.DENY_MSG('Stats test failure')
+ end
+end
+
+function reply_result(state, req)
+ local got = stats.list()
+ log_info(ffi.C.LOG_GRP_TESTS, 'current stats.list() values:')
+ print(table_print(got))
+ local result = check_stats(got)
+ return result(state, req)
+end
+policy.add(policy.pattern(reply_result, 'stats.test.'))
+policy.add(policy.all(FWD_TARGET)) -- avoid iteration
+
+-- make sure DNSSEC is turned off for tests
+trust_anchors.remove('.')
+
+-- Enable queries without RD bit
+pcall(modules.unload, 'refuse_nord')
+
+-- 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
+
+_hint_root_file('hints')
+cache.size = 2*MB
+log_level('debug')
+{% endraw %}
+
+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(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 --git a/modules/stats/test.integr/stats.rpl b/modules/stats/test.integr/stats.rpl
new file mode 100644
index 0000000..ecab062
--- /dev/null
+++ b/modules/stats/test.integr/stats.rpl
@@ -0,0 +1,194 @@
+; SPDX-License-Identifier: GPL-3.0-or-later
+ trust-anchor: "example. DNSKEY 257 3 7 AwEAAcUlFV1vhmqx6NSOUOq2R/dsR7Xm3upJ ( j7IommWSpJABVfW8Q0rOvXdM6kzt+TAu92L9 AbsUdblMFin8CVF3n4s= )"
+CONFIG_END
+
+SCENARIO_BEGIN Test stats module
+
+RANGE_BEGIN 0 100
+ ADDRESS 192.0.2.1
+
+ENTRY_BEGIN
+REPLY QR RA RD CD NOERROR
+MATCH opcode question rcode
+ADJUST copy_id
+SECTION QUESTION
+cd.test. IN TXT
+SECTION ANSWER
+cd.test. IN TXT "CD is set"
+ENTRY_END
+
+ENTRY_BEGIN
+REPLY QR RA RD CD NOERROR
+MATCH opcode question rcode
+ADJUST copy_id
+SECTION QUESTION
+nodata.test. IN TXT
+ENTRY_END
+
+ENTRY_BEGIN
+REPLY QR RA RD CD NXDOMAIN
+MATCH opcode question
+ADJUST copy_id
+SECTION QUESTION
+nxdomain.test. IN TXT
+ENTRY_END
+
+; failing DNSSEC-signed subdomain
+ENTRY_BEGIN
+REPLY QR RA RD CD SERVFAIL
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+SECTION QUESTION
+bogus.test. IN TXT
+ENTRY_END
+
+; query for this name triggers check in Lua config
+ENTRY_BEGIN
+REPLY QR RA RD CD NOERROR
+MATCH opcode question rcode
+ADJUST copy_id
+SECTION QUESTION
+stats.test. IN TXT
+SECTION ANSWER
+stats.test. IN TXT "Ok, trigger query was not intercepted!"
+ENTRY_END
+
+ENTRY_BEGIN
+REPLY QR RD RA CD TC NOERROR
+MATCH opcode question rcode
+ADJUST copy_id
+SECTION QUESTION
+tc.test. IN URI
+ENTRY_END
+
+RANGE_END
+
+
+; +cd +rd
+STEP 10 QUERY
+ENTRY_BEGIN
+REPLY RD CD NOERROR
+SECTION QUESTION
+cd.test. IN TXT
+ENTRY_END
+
+STEP 11 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA CD NOERROR
+SECTION QUESTION
+cd.test. IN TXT
+SECTION ANSWER
+cd.test. IN TXT "CD is set"
+ENTRY_END
+
+; +cd +cached +rd
+STEP 12 QUERY
+ENTRY_BEGIN
+REPLY RD CD NOERROR
+SECTION QUESTION
+cd.test. IN TXT
+ENTRY_END
+
+STEP 13 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA CD NOERROR
+SECTION QUESTION
+cd.test. IN TXT
+SECTION ANSWER
+cd.test. IN TXT "CD is set"
+ENTRY_END
+
+; +nodata +rd
+STEP 20 QUERY
+ENTRY_BEGIN
+REPLY RD NOERROR
+SECTION QUESTION
+nodata.test. IN TXT
+SECTION ADDITIONAL
+ENTRY_END
+
+STEP 21 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY QR RD RA NOERROR
+MATCH all
+SECTION QUESTION
+nodata.test. IN TXT
+ENTRY_END
+
+; +nxdomain +rd
+STEP 30 QUERY
+ENTRY_BEGIN
+REPLY RD NOERROR
+SECTION QUESTION
+nxdomain.test. IN TXT
+SECTION ADDITIONAL
+ENTRY_END
+
+STEP 31 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY QR RD RA NXDOMAIN
+MATCH all
+SECTION QUESTION
+nxdomain.test. IN TXT
+ENTRY_END
+
+; +servfail +do +rd
+STEP 40 QUERY
+ENTRY_BEGIN
+REPLY RD DO NOERROR
+SECTION QUESTION
+bogus.test. IN TXT
+SECTION ADDITIONAL
+ENTRY_END
+
+STEP 41 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY QR RD RA DO SERVFAIL
+MATCH all
+SECTION QUESTION
+bogus.test. IN TXT
+ENTRY_END
+
+; no rd
+STEP 50 QUERY
+ENTRY_BEGIN
+REPLY NOERROR
+SECTION QUESTION
+bogus.test. IN TXT
+SECTION ADDITIONAL
+ENTRY_END
+
+STEP 51 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY QR RA SERVFAIL
+MATCH all
+SECTION QUESTION
+bogus.test. IN TXT
+ENTRY_END
+
+
+
+
+STEP 100 QUERY
+ENTRY_BEGIN
+REPLY RD NOERROR
+SECTION QUESTION
+stats.test. IN TXT
+SECTION ADDITIONAL
+ENTRY_END
+
+STEP 101 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY NOERROR
+MATCH opcode question additional rcode answer
+; AD must not be set in the answer
+SECTION QUESTION
+stats.test. IN TXT
+SECTION ANSWER
+stats.test. IN TXT "Ok, trigger query was not intercepted!"
+ENTRY_END
+
+
+SCENARIO_END
diff --git a/modules/ta_sentinel/.packaging/test.config b/modules/ta_sentinel/.packaging/test.config
new file mode 100644
index 0000000..4bb6ac9
--- /dev/null
+++ b/modules/ta_sentinel/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('ta_sentinel')
+assert(ta_sentinel)
+quit()
diff --git a/modules/ta_sentinel/README.rst b/modules/ta_sentinel/README.rst
new file mode 100644
index 0000000..aa9d733
--- /dev/null
+++ b/modules/ta_sentinel/README.rst
@@ -0,0 +1,18 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-ta_sentinel:
+
+Sentinel for Detecting Trusted Root Keys
+========================================
+
+The module ``ta_sentinel`` implements A Root Key Trust Anchor Sentinel for DNSSEC
+according to standard :rfc:`8509`.
+
+This feature allows users of DNSSEC validating resolver to detect which root keys
+are configured in resolver's chain of trust. The data from such
+signaling are necessary to monitor the progress of the DNSSEC root key rollover
+and to detect potential breakage before it affect users. One example of research enabled by this module `is available here <https://www.potaroo.net/ispcol/2018-11/kskpm.html>`_.
+
+This module is enabled by default and we urge users not to disable it.
+If it is absolutely necessary you may add ``modules.unload('ta_sentinel')``
+to your configuration to disable it.
diff --git a/modules/ta_sentinel/ta_sentinel.lua b/modules/ta_sentinel/ta_sentinel.lua
new file mode 100644
index 0000000..5a2654b
--- /dev/null
+++ b/modules/ta_sentinel/ta_sentinel.lua
@@ -0,0 +1,80 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local M = {}
+M.layer = {}
+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]
+ if label_len ~= 29 and label_len ~= 30 then
+ return state end
+ -- end of hot path
+
+ local qtype = pkt:qtype()
+ if not (qtype == kres.type.A or qtype == kres.type.AAAA) then
+ return state end
+ if bit.band(state, kres.FAIL) ~= 0 then
+ return state end
+
+ -- check the label name
+ local qry = req:resolved()
+ local qname = kres.dname2str(qry:name()):lower()
+ local sentype, keytag
+ if label_len == 29 then
+ sentype = true
+ keytag = qname:match('^root%-key%-sentinel%-is%-ta%-(%x+)%.')
+ elseif label_len == 30 then
+ sentype = false
+ keytag = qname:match('^root%-key%-sentinel%-not%-ta%-(%x+)%.')
+ end
+ if not keytag then return state end
+
+ if req.rank ~= ffi.C.KR_RANK_SECURE or req.answer:cd() then
+ log_info(ffi.C.LOG_GRP_TASENTINEL, 'name+type OK but not AD+CD conditions')
+ return state
+ end
+
+ -- check keytag from the label
+ keytag = tonumber(keytag)
+ if not keytag or math.floor(keytag) ~= keytag then
+ return state end -- pattern did not match, exit
+ if keytag < 0 or keytag > 0xffff then
+ return state end -- invalid keytag?!, exit
+
+ log_info(ffi.C.LOG_GRP_TASENTINEL, 'key tag: ' .. keytag .. ', sentinel: ' .. tostring(sentype))
+
+ local found = false
+ local ds_set = ffi.C.kr_ta_get(kres.context().trust_anchors, '\0')
+ if ds_set ~= nil then
+ for i = 0, ds_set:rdcount() - 1 do
+ -- Find the key tag in rdata and compare
+ -- https://tools.ietf.org/html/rfc4034#section-5.1
+ local rdata = ds_set:rdata_pt(i)
+ local tag = rdata.data[0] * 256 + rdata.data[1]
+ if tag == keytag then
+ found = true
+ end
+ end
+ end
+ log_info(ffi.C.LOG_GRP_TASENTINEL, 'matching trusted TA found: ' .. tostring(found))
+ if not found then -- print matching TAs in *other* states than Valid
+ for i = 1, #(trust_anchors.keysets['\0'] or {}) do
+ local key = trust_anchors.keysets['\0'][i]
+ if key.key_tag == keytag and key.state ~= 'Valid' then
+ log_info(ffi.C.LOG_GRP_TASENTINEL, 'matching UNtrusted TA found in state: '
+ .. key.state)
+ end
+ end
+ end
+
+ if sentype ~= found then -- expected key is not there, or unexpected key is there
+ pkt:clear_payload()
+ pkt:rcode(kres.rcode.SERVFAIL)
+ pkt:ad(false)
+ end
+ return state -- do not break resolution process
+end
+
+return M
diff --git a/modules/ta_signal_query/.packaging/test.config b/modules/ta_signal_query/.packaging/test.config
new file mode 100644
index 0000000..dfa7c2a
--- /dev/null
+++ b/modules/ta_signal_query/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('ta_signal_query')
+assert(ta_signal_query)
+quit()
diff --git a/modules/ta_signal_query/README.rst b/modules/ta_signal_query/README.rst
new file mode 100644
index 0000000..3136ecb
--- /dev/null
+++ b/modules/ta_signal_query/README.rst
@@ -0,0 +1,31 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-ta_signal_query:
+
+Signaling Trust Anchor Knowledge in DNSSEC
+==========================================
+
+The module for Signaling Trust Anchor Knowledge in DNSSEC Using Key Tag Query,
+implemented according to :rfc:`8145#section-5`.
+
+This feature allows validating resolvers to signal to authoritative servers
+which keys are referenced in their chain of trust. The data from such
+signaling allow zone administrators to monitor the progress of rollovers
+in a DNSSEC-signed zone.
+
+This mechanism serve to measure the acceptance and use of new DNSSEC
+trust anchors and key signing keys (KSKs). This signaling data can be
+used by zone administrators as a gauge to measure the successful deployment
+of new keys. This is of particular interest for the DNS root zone in the event
+of key and/or algorithm rollovers that rely on :rfc:`5011` to automatically
+update a validating DNS resolver’s trust anchor.
+
+.. attention::
+ Experience from root zone KSK rollover in 2018 shows that this mechanism
+ by itself is not sufficient to reliably measure acceptance of the new key.
+ Nevertheless, some DNS researchers found it is useful in combination
+ with other data so we left it enabled for now. This default might change
+ once more information is available.
+
+This module is enabled by default. You may use ``modules.unload('ta_signal_query')``
+in your configuration.
diff --git a/modules/ta_signal_query/ta_signal_query.lua b/modules/ta_signal_query/ta_signal_query.lua
new file mode 100644
index 0000000..72c1568
--- /dev/null
+++ b/modules/ta_signal_query/ta_signal_query.lua
@@ -0,0 +1,64 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- Module implementing RFC 8145 section 5
+-- Signaling Trust Anchor Knowledge in DNS using Key Tag Query
+local kres = require('kres')
+local ffi = require('ffi')
+
+local M = {}
+M.layer = {}
+
+-- transform trust anchor keyset structure for one domain name (in wire format)
+-- to signalling query name like _ta-keytag1-keytag2.example.com.
+-- Returns:
+-- string constructed from valid keytags
+-- nil if no valid keytag is present in keyset
+local function prepare_query_name(keyset, name)
+ if not keyset then return nil end
+ local keytags = {}
+ for _, key in ipairs(keyset) do
+ if key.state == "Valid" then
+ table.insert(keytags, key.key_tag)
+ end
+ end
+ if next(keytags) == nil then return nil end
+
+ table.sort(keytags)
+ local query = "_ta"
+ for _, tag in pairs(keytags) do
+ query = string.format("%s-%04x", query, tag)
+ end
+ if name == "\0" then
+ return query .. "."
+ else
+ return query .. "." .. kres.dname2str(name)
+ end
+end
+
+-- construct keytag query for valid keys and send it as asynchronous query
+-- (does nothing if no valid keys are present at given domain name)
+local function send_ta_query(domain)
+ local keyset = trust_anchors.keysets[domain]
+ local qname = prepare_query_name(keyset, domain)
+ if qname ~= nil then
+ log_info(ffi.C.LOG_GRP_TASIGNALING, "signalling query triggered: %s", qname)
+ -- asynchronous query
+ -- we do not care about result or from where it was obtained
+ event.after(0, function ()
+ resolve(qname, kres.type.NULL, kres.class.IN, "NONAUTH")
+ end)
+ end
+end
+
+-- act on DNSKEY queries which were not answered from cache
+function M.layer.consume(state, req, pkt)
+ -- First check for standard "cached packets": PKT_SIZE_NOWIRE, for efficiency.
+ if pkt.size ~= -1 and pkt:qtype() == kres.type.DNSKEY then
+ local qry = req:current()
+ if not qry.flags.CACHED then
+ send_ta_query(qry:name())
+ end
+ end
+ return state -- do not interfere with normal query processing
+end
+
+return M
diff --git a/modules/ta_update/.packaging/test.config b/modules/ta_update/.packaging/test.config
new file mode 100644
index 0000000..5fe5587
--- /dev/null
+++ b/modules/ta_update/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('ta_update')
+assert(ta_update)
+quit()
diff --git a/modules/ta_update/meson.build b/modules/ta_update/meson.build
new file mode 100644
index 0000000..e1c074e
--- /dev/null
+++ b/modules/ta_update/meson.build
@@ -0,0 +1,21 @@
+# LUA module: ta_update
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+config_tests += [
+ ['ta_update', files('ta_update.test.lua'), ['snowflake']],
+]
+
+integr_tests += [
+ # 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', 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 --git a/modules/ta_update/root.keys b/modules/ta_update/root.keys
new file mode 100644
index 0000000..e292b5a
--- /dev/null
+++ b/modules/ta_update/root.keys
@@ -0,0 +1 @@
+. IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D
diff --git a/modules/ta_update/ta_update.lua b/modules/ta_update/ta_update.lua
new file mode 100644
index 0000000..2361e16
--- /dev/null
+++ b/modules/ta_update/ta_update.lua
@@ -0,0 +1,349 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- Module interface
+local ffi = require('ffi')
+local kres = require('kres')
+local C = ffi.C
+
+assert(trust_anchors, 'ta_update module depends on initialized trust_anchors library')
+local key_state = trust_anchors.key_state
+assert(key_state)
+
+local ta_update = {}
+local tracked_tas = {} -- zone name (wire) => {event = number}
+
+
+-- Find key in current keyset
+local function ta_find(keyset, rr)
+ local rr_tag = C.kr_dnssec_key_tag(rr.type, rr.rdata, #rr.rdata)
+ if rr_tag < 0 or rr_tag > 65535 then
+ log_warn(ffi.C.LOG_GRP_TAUPDATE, string.format('ignoring invalid or unsupported RR: %s: %s',
+ kres.rr2str(rr), ffi.string(C.knot_strerror(rr_tag))))
+ return nil
+ end
+ for i, ta in ipairs(keyset) do
+ -- Match key owner and content
+ local ta_tag = C.kr_dnssec_key_tag(ta.type, ta.rdata, #ta.rdata)
+ if ta_tag < 0 or ta_tag > 65535 then
+ log_warn(ffi.C.LOG_GRP_TAUPDATE, string.format('[ta_update] ignoring invalid or unsupported RR: %s: %s',
+ kres.rr2str(ta), ffi.string(C.knot_strerror(ta_tag))))
+ else
+ if ta.owner == rr.owner then
+ if ta.type == rr.type then
+ if rr.type == kres.type.DNSKEY then
+ if C.kr_dnssec_key_match(ta.rdata, #ta.rdata, rr.rdata, #rr.rdata) == 0 then
+ return ta
+ end
+ elseif rr.type == kres.type.DS and ta.rdata == rr.rdata then
+ return ta
+ end
+ -- DNSKEY superseding DS, inexact match
+ elseif rr.type == kres.type.DNSKEY and ta.type == kres.type.DS then
+ if ta.key_tag == rr_tag then
+ keyset[i] = rr -- Replace current DS
+ rr.state = ta.state
+ rr.key_tag = ta.key_tag
+ return rr
+ end
+ -- DS key matching DNSKEY, inexact match
+ elseif rr.type == kres.type.DS and ta.type == kres.type.DNSKEY then
+ if rr_tag == ta_tag then
+ return ta
+ end
+ end
+ end
+ end
+ end
+ return nil
+end
+
+-- 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
+ log_warn(ffi.C.LOG_GRP_TAUPDATE, 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
+ 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 then
+ log_info(ffi.C.LOG_GRP_TAUPDATE, '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
+end
+
+-- TA is missing in the new key set. The time is in seconds.
+local function ta_missing(ta, hold_down_time)
+ -- Key is removed (KeyRem)
+ local keep_ta = true
+ local key_tag = C.kr_dnssec_key_tag(ta.type, ta.rdata, #ta.rdata)
+ if key_tag < 0 or key_tag > 65535 then
+ log_warn(ffi.C.LOG_GRP_TAUPDATE, string.format('[ta_update] ignoring invalid or unsupported RR: %s: %s',
+ kres.rr2str(ta), ffi.string(C.knot_strerror(key_tag))))
+ key_tag = ''
+ end
+ if ta.state == key_state.Valid then
+ ta.state = key_state.Missing
+ ta.timer = os.time() + hold_down_time
+
+ -- Remove key that is missing for too long
+ elseif ta.state == key_state.Missing and os.difftime(ta.timer, os.time()) <= 0 then
+ ta.state = key_state.Removed
+ log_info(ffi.C.LOG_GRP_TAUPDATE, 'key: '..key_tag..' removed because missing for too long')
+ keep_ta = false
+
+ -- Purge pending key
+ elseif ta.state == key_state.AddPend then
+ log_info(ffi.C.LOG_GRP_TAUPDATE, 'key: '..key_tag..' purging')
+ keep_ta = false
+ end
+ log_info(ffi.C.LOG_GRP_TAUPDATE, 'key: '..key_tag..' state: '..ta.state)
+ return keep_ta
+end
+
+-- Update existing keyset; return true if successful.
+local function update(keyset, new_keys)
+ if not new_keys then return false end
+ if not keyset.managed then
+ -- this may happen due to race condition during testing in CI (refresh time < query time)
+ return false
+ end
+
+ -- Filter TAs to be purged from the keyset (KeyRem), in three steps
+ -- 1: copy TAs to be kept to `keepset`
+ local hold_down = (keyset.hold_down_time or ta_update.hold_down_time) / 1000
+ local keepset = {}
+ local keep_removed = keyset.keep_removed or ta_update.keep_removed
+ for _, ta in ipairs(keyset) do
+ local keep = true
+ if not ta_find(new_keys, ta) then
+ -- Ad-hoc: RFC 5011 doesn't mention removing a Missing key.
+ -- Let's do it after a very long period has elapsed.
+ keep = ta_missing(ta, hold_down * 4)
+ end
+ -- Purge removed keys
+ if ta.state == key_state.Removed then
+ if keep_removed > 0 then
+ keep_removed = keep_removed - 1
+ else
+ keep = false
+ end
+ end
+ if keep then
+ table.insert(keepset, ta)
+ end
+ end
+ -- 2: remove all TAs - other settings etc. will remain in the keyset
+ for i, _ in ipairs(keyset) do
+ keyset[i] = nil
+ end
+ -- 3: move TAs to be kept into the keyset (same indices)
+ for k, ta in pairs(keepset) do
+ keyset[k] = ta
+ end
+
+ -- Evaluate new TAs
+ for _, rr in ipairs(new_keys) do
+ if (rr.type == kres.type.DNSKEY or rr.type == kres.type.DS) and rr.rdata ~= nil then
+ ta_present(keyset, rr, hold_down)
+ end
+ end
+
+ -- Store the keyset
+ trust_anchors.keyset_write(keyset)
+
+ -- Start using the new TAs.
+ if not trust_anchors.keyset_publish(keyset) then
+ -- TODO: try to rebootstrap if for root?
+ return false
+ else
+ log_debug(ffi.C.LOG_GRP_TAUPDATE, 'refreshed trust anchors for domain ' .. kres.dname2str(keyset.owner) .. ' are:\n'
+ .. trust_anchors.summary(keyset.owner))
+ end
+
+ return true
+end
+
+local function unmanagedkey_change(file_name)
+ log_warn(ffi.C.LOG_GRP_TAUPDATE, '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, req, managed)
+ local retry = true
+
+ 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
+ if rr.type == kres.type.DNSKEY then
+ table.insert(new_keys, rr)
+ end
+ end
+
+ if managed then
+ update(keyset, new_keys)
+ else
+ check_upstream(keyset, new_keys)
+ end
+ retry = false
+ else
+ local qry = req:initial()
+ if qry.flags.DNSSEC_BOGUS == true then
+ log_warn(ffi.C.LOG_GRP_TAUPDATE, 'active refresh failed, update your trust anchors in "%s"', keyset.filename)
+ elseif pkt == nil then
+ log_warn(ffi.C.LOG_GRP_TAUPDATE, 'active refresh failed, answer was dropped')
+ else
+ log_warn(ffi.C.LOG_GRP_TAUPDATE, '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
+ for _, rr in ipairs(keyset) do -- 10 or 50% of the original TTL
+ min_ttl = math.min(min_ttl, (retry and 100 or 500) * rr.ttl)
+ end
+ return math.max(hour, min_ttl)
+end
+
+-- Plan an event for refreshing DNSKEYs and re-scheduling itself
+local function refresh_plan(keyset, delay, managed)
+ local owner = keyset.owner
+ local owner_str = kres.dname2str(keyset.owner)
+ if not tracked_tas[owner] then
+ tracked_tas[owner] = {}
+ end
+ local track_cfg = tracked_tas[owner]
+ if track_cfg.event then -- restart timer if necessary
+ event.cancel(track_cfg.event)
+ end
+ track_cfg.event = event.after(delay, function ()
+ log_info(ffi.C.LOG_GRP_TAUPDATE, 'refreshing TA for ' .. owner_str)
+ resolve(owner_str, kres.type.DNSKEY, kres.class.IN, 'NO_CACHE',
+ function (pkt, req)
+ -- Schedule itself with updated timeout
+ local delay_new = active_refresh(keyset, pkt, req, managed)
+ delay_new = keyset.refresh_time or ta_update.refresh_time or delay_new
+ log_info(ffi.C.LOG_GRP_TAUPDATE, 'next refresh for ' .. owner_str .. ' in '
+ .. delay_new/hour .. ' hours')
+ refresh_plan(keyset, delay_new, managed)
+ end)
+ end)
+end
+
+ta_update = {
+ -- [optional] overrides for global defaults of
+ -- hold_down_time, refresh_time, keep_removed
+ hold_down_time = 30 * day,
+ 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 immediately
+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
+ refresh_plan(keyset, 0, managed)
+end
+
+function ta_update.stop(zname)
+ if tracked_tas[zname] then
+ event.cancel(tracked_tas[zname].event)
+ tracked_tas[zname] = nil
+ trust_anchors.keysets[zname].managed = false
+ end
+end
+
+-- stop all timers
+function ta_update.deinit()
+ for zname, _ in pairs(tracked_tas) do
+ ta_update.stop(zname)
+ end
+end
+
+return ta_update
diff --git a/modules/ta_update/ta_update.test.integr/deckard.yaml b/modules/ta_update/ta_update.test.integr/deckard.yaml
new file mode 100644
index 0000000..1d005e3
--- /dev/null
+++ b/modules/ta_update/ta_update.test.integr/deckard.yaml
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+programs:
+- name: kresd
+ binary: kresd
+ additional:
+ - --noninteractive
+ templates:
+ - modules/ta_update/ta_update.test.integr/kresd_config.j2
+ - tests/integration/hints_zone.j2
+ configs:
+ - config
+ - hints
diff --git a/modules/ta_update/ta_update.test.integr/kresd_config.j2 b/modules/ta_update/ta_update.test.integr/kresd_config.j2
new file mode 100644
index 0000000..da319a2
--- /dev/null
+++ b/modules/ta_update/ta_update.test.integr/kresd_config.j2
@@ -0,0 +1,56 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+{% for TAF in TRUST_ANCHOR_FILES %}
+trust_anchors.add_file('{{TAF}}')
+{% endfor %}
+
+{% raw %}
+
+-- 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
+
+policy.add(policy.suffix(policy.PASS, {todname('test.')}))
+cache.size = 2*MB
+log_level('debug')
+{% endraw %}
+
+modules.load('hints')
+hints.root({['{{ROOT_NAME}}'] = '{{ROOT_ADDR}}'})
+
+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(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 --git a/modules/ta_update/ta_update.test.integr/rfc5011-monotonictime.rpl b/modules/ta_update/ta_update.test.integr/rfc5011-monotonictime.rpl
new file mode 100644
index 0000000..c9e2204
--- /dev/null
+++ b/modules/ta_update/ta_update.test.integr/rfc5011-monotonictime.rpl
@@ -0,0 +1,5755 @@
+stub-addr: 2001:503:ba3e::2:30
+stub-name: rootns.
+trust-anchor: . IN DS 1867 8 2 EBF6C553C9DDABFB3522DFD4E62A857D9E00E373686C3479064B46BF6E43AC5E
+val-override-date: 20170701000000
+query-minimization: off
+CONFIG_END
+
+SCENARIO_BEGIN Successfull RFC 5011 KSK roll-over simulator for 2017
+
+
+RANGE_BEGIN 20170701000000 20170710999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20170715000000 20170701000000 37284 . MXzziCbaLNyN/I6vS5rNpEua0moTNYjG ICy9SgiSs4yNvyZ+Kn/1puEbaxLJ2M6J vVjVCbJMVTrINvLmgVcG3UphIFBdrgfo FZkav2nHbybKao5WafStZzGpKQJkBTeA fqKMXuPtKwM1g9Tzwr2YxdLVoGzBYA/t s41uKHI0/1YcU9nMadlmY/3rJPFOUIrl iCwskFGmLVickcRnp/z0FmTd1k67wSrn z16Ys7xeQknHOlr5DqBtezicRE63srvv j8QuOL5wLhknmmqnntVYkYXHKsZpWsEE OWxLc4PwbDvVI5LDZ3NmCAH0k7ygc5/s WsnAKKvzDfT5lXtudOkCWg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAeh0qmH7cGmSjplVMqU27YkofOHp tt6deTnAprIDS5lws0Y3vFm+NNEDaZHB SHqIbxEFW5uX33rQ5EfCFQI1gdfjCtCi obwPvu04P6t2cuP9qSusTXGlr9qaqNCh ntErj1oK+2QH1S86ttgepzv4kF9y/Ee4 B4YQttSLuTcWO9bZR3v5QWT4581bPlMO Oj3Yo2Ubs81qtOoiI2grq7Yn7x/sTt43 pDstuS7wwaOnKb36S5BAMBDwZCXRFeDV sm0EseG5qeu0K6E8AOVE3wjOePetzpDE 6O0k0BM6QlPZ3EttnNgqVyZFZwHZu8vM Yoo5zypyH58AAGAONZKwQeg3nak=
+. 1814400 IN DNSKEY 256 3 8 AwEAAe1udmhwaWyg6PD00YReHeT6nYeN hKAy6oKMc7X5NXXJq6baVZT5p3AnFm7B 0ReT1dOpOjZuArydSdASupCQwRPVja+m 8PQSaAdThTifIBbP0Y2TeeA3yfGbHubo scMezJAXr7FGDQ8XIfyUYBDBoDaINi6j V/oI+aSnwm0uWdxHqfonzioTYenuTsax OGLYYuCMn9soxIuXatO53MpqfsJgh7UK FDCDGKA9pFts1vTQdCMPGgEsuYHjsYF0 /kUoRAQJM3jh0t3wE3JQ/HjFIyR37NCM w+i20b1v6/Xo86gy2nhXq2BopawLPEh0 Fv2UCCI7f8k2JvvF2o9udq/N+FE=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcgeCFZiwMD0S6TVgSK3Ob/MKFo+ dHYeNgoOjxH3JoxV09WShBXID/LwKs/e sYAIzTOBB8Fu9IDoVuQe5prOEcGZRp6u zF7JLnqrgOzoaSPRmKsvXh8DfipA56qE AqNwYBCqzlbGIGjHOQ8t+Xonp4fSBYbN MLmh7o8+u55k7PxemKTCiAnY7Bjx0g3m J1HuFvQXDDdvyletuoS3NKqrkjqdOHRB 3p3GKrKgw+zVb05UEyotCEKjqqi+BOmg cm8rmUIeaNQREjxrUyr8/Ry67h5NJLfq LAqAnSePf1PyhQcl8XjGHXN7w1tLLyJu Gy5B3zLSW1P6jIjon77PSn+cXi0=
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20170722000000 20170701000000 1867 . aCKUdEnbwdvfHHiGS4xzUB/H+wdSgyqs 2YiGyLAnd8R/u1LpQ2hqEzSMqSr7blfL OLMIy/SA3PulAdPbBub5aM9qAveqc+Mc +vOIugZrWHYOECPXmJsmNBj8VT5FEOuw N+PzReRG4P6SzyZ4DckIcfpIjikCHPS/ 6JEkkYCzktx/DcKmI0kQtGIUHXrwvaTn 7G8fjZpZUUBwG0IKYg629vo5IgVlmPPr kvsLWgQuKu6pu30iA3DmIAI6AX0O2xnh HsUzliRPdt7WgJCCX+knJr4d9rv3FsGi /VvCeQhBxjWVQBKMlfvnWagp820muw0X UwHnzEkIRQyv9JDabdJtQg==
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20170722000000 20170701000000 37284 . KpkR9ceTqCxkKT7Gbq0poQhiv19qI1BD ahGsTJheWlruVH3wSQ2OsAl4pkmS+ich 1065M/OsL/3EHAbYsZ9yskhPJwyeZVDP cgfEusQxMekz+0s98jHt/QqgKBJdiF2F uu/krrYctAJos4i6P93paKHmuji7tiL2 JBuENw5G+WfqGBUciI3ca9QMDPbHWS4z bgW6ViSFdVC/D3l+ZULoAB/FBQykAESn /hGGbDBfG1wfkngfXkByTLKzWzSQsIOr krbJ38W3Sm/1GjAZdesJBJQcxm6e2dyN N3j51hZTH23dvEzqCcRCVqkeCDRlzxPc YGteAaRAnMNh/gB4r7AUbw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20170715000000 20170701000000 37284 . TV/Ad1roCxCazfDEk1UvRiGIelQDmQIx yHEtce3db9fTcag3fvSj+swbAiHoYaV8 gm7ZWaI/rBuoi4WIeHPGmtc5WF8uTpgK EmIO5DzVYubZQ9o3KdYxe4fqHm0LZovX H+7eLsUcMHavzhrRx0KvrHQQ3mxMGIGv cxwO1WDhAAj1tlkkix1Rwxwf59lIBB6s Y+eXI2TKVe9gnVSjx/YHkVeRxB4lHhWy cCWiqYjgKmeu+tlGorcI9gwvjc0/CFnL fnk3cnWD3RdnNyg4qnqpziqkCWrZ0Vae KJ/nDWmObViBKclYVSGMMv/O1HCQS6/M rYo2HKURPYvWqjUr+JOq3Q==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20170715000000 20170701000000 37284 . bqmvP3LjtKK+owiC+MpcZeYM3xmHUpEX YEMqCQmFL7Pa7dOBJIedO9ZXNBV6RjTI 3MY1VEtQxsUu0QbdCsjHksZw6N1ovHoq PyPssQF1WZsNZan8fMPdtvByKBjbM5qL AGF3IeAnIMYEaPDovDBaBXFh3JqLBrV2 kDM5/ddyI0NgFr/XFcrEyCCSXzEqzEFC J0Bps1+cleRZT4ypUA0UYH2/feoBHV// jhG1BQqoGFQwvQnTUVNxcmZpJa+jNCnL YrDwUmbRx0drh++9uFhdWL7nElWKejcj 3tt02FnAwASKz6VpFGVk0Iu7r/OFAf3a r/KB3beYk+ahMbHl53+gJg==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170715000000 20170701000000 37284 . 0VMyTLgwifNoeM9EWiRRD5m4I6Hmg/va vRnhLSv4l3+v7l1FakRjGQ1Ov9bEs5sR vyzFlr0D9TwCQyMD9pnE3kiWpwtFYLaS RRSM9rPbSQ9+UnZ0OMvg5k1g+wQ/ssS4 vLduDlTXPXrwdg8NrAM3RhI2lAanPrGU MSu+eWNOvv2hu9x1L+I5orbMELJfjSvu 30hEa1HUJmvC6oyshWBIbQtiWWPRqXo2 AuwT+IpZrR87FsCQxxrAX8ptxnKI8A19 SOLfiHMXXAoHxF3xZvTR5ZO1zpx6YL8O cwXkln+6OMIPlrlQ60vrRIXmBiJcJq9q Bn2ER5//+J3fzmmeRWhodQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20170715000000 20170701000000 37284 . MXzziCbaLNyN/I6vS5rNpEua0moTNYjG ICy9SgiSs4yNvyZ+Kn/1puEbaxLJ2M6J vVjVCbJMVTrINvLmgVcG3UphIFBdrgfo FZkav2nHbybKao5WafStZzGpKQJkBTeA fqKMXuPtKwM1g9Tzwr2YxdLVoGzBYA/t s41uKHI0/1YcU9nMadlmY/3rJPFOUIrl iCwskFGmLVickcRnp/z0FmTd1k67wSrn z16Ys7xeQknHOlr5DqBtezicRE63srvv j8QuOL5wLhknmmqnntVYkYXHKsZpWsEE OWxLc4PwbDvVI5LDZ3NmCAH0k7ygc5/s WsnAKKvzDfT5lXtudOkCWg==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20170715000000 20170701000000 37284 . Umcn1NDbjEEmeIjK2V5JgIjD45fh+CMw ry834Q7hCK838ePxPVGt3eqXsD/evlk4 WD72Ir49kb7P2rtPqPSdYZBJy0qRiznZ +9+IoXSwezSfMYAze4rb2Qng2TjPEgan ptrFAFBNrRyQB83QLqhUKMHoqINyX+yJ WhMmO/KoEYzg+VdttS1VOi61TL6CivjL 5WFwO5oBHyj91kudvt0LQ58wSndlXzJ2 NwX8tjJcBfyp2tMItFn7c4mMjZyHKxrt tnJkP743NH4Og84ie4QSu4PaVWk+T3+Y n9pg0Dxc2URqbI2OXN0Ggi5i17zdyAeC 6mjA60qAJEKfacqJrqh4nA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20170715000000 20170701000000 37284 . bqmvP3LjtKK+owiC+MpcZeYM3xmHUpEX YEMqCQmFL7Pa7dOBJIedO9ZXNBV6RjTI 3MY1VEtQxsUu0QbdCsjHksZw6N1ovHoq PyPssQF1WZsNZan8fMPdtvByKBjbM5qL AGF3IeAnIMYEaPDovDBaBXFh3JqLBrV2 kDM5/ddyI0NgFr/XFcrEyCCSXzEqzEFC J0Bps1+cleRZT4ypUA0UYH2/feoBHV// jhG1BQqoGFQwvQnTUVNxcmZpJa+jNCnL YrDwUmbRx0drh++9uFhdWL7nElWKejcj 3tt02FnAwASKz6VpFGVk0Iu7r/OFAf3a r/KB3beYk+ahMbHl53+gJg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170715000000 20170701000000 37284 . 0VMyTLgwifNoeM9EWiRRD5m4I6Hmg/va vRnhLSv4l3+v7l1FakRjGQ1Ov9bEs5sR vyzFlr0D9TwCQyMD9pnE3kiWpwtFYLaS RRSM9rPbSQ9+UnZ0OMvg5k1g+wQ/ssS4 vLduDlTXPXrwdg8NrAM3RhI2lAanPrGU MSu+eWNOvv2hu9x1L+I5orbMELJfjSvu 30hEa1HUJmvC6oyshWBIbQtiWWPRqXo2 AuwT+IpZrR87FsCQxxrAX8ptxnKI8A19 SOLfiHMXXAoHxF3xZvTR5ZO1zpx6YL8O cwXkln+6OMIPlrlQ60vrRIXmBiJcJq9q Bn2ER5//+J3fzmmeRWhodQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20170715000000 20170701000000 37284 . h9R23s9ZHXVX5wcCV7ToIE4dTQ1fAGqw ehpmzvbblti8phOdm+/u2ActSH4VMLo1 ze1U7vVuSuNMII4l27vARn6XFwhdHYqh lril9Yu6iyx/sKyqbLoyzQ0aD9E1SnO2 aRNWdpjeIYvw7cwvqFaH4bj854eB2clc 7s1olLEkQmZ7oBWKx9+v73Pg587Ky3Z1 Cw5f+iLa0ld/t6m8BtAmRjYc9hj1s0ph 65SzaU4KZKNikG0QVsNC1AnpFbrYSbWD MAEyp6cU/KfF7VrKGaIgWu+Z8eU1v0fN BOsPkfBooSJj2v5nded2W+yCV+plZGVj pdTESXNoZYLyvgsX6DoDYQ==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20170711000000 20170720999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20170725000000 20170711000000 37284 . 2OY96fDuZ/ZCQxiryCYeSknlLFG3btVH W15h88UgKw5z0eLmBMUO95tPlSjF5A/Z PgQT6+qXXJMoLNxPGeTsNGl+oPxXCeFq 7+egtR1KLNafoYb8TgpW9I/lsKi8KwY9 tL1ySvoultD8Vd4RYx5MD/T+NI1f5rk7 y7UxPngi5zIw+GMZnGWSdJW3qhevUGLN l5wFjFhSleLXO1JkCzM+fxQJgR+qLJk0 0YfFK6TCEPwMBybwWN6/NuRWotfc57au 7ZavTEPDDLpWivElJyMH1/BSUviHbJI5 brQsdc91jADXp+W9lCpwNDNs5iU5WdJv rN8tqpp/MHJedmYMW67jdg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAe1udmhwaWyg6PD00YReHeT6nYeN hKAy6oKMc7X5NXXJq6baVZT5p3AnFm7B 0ReT1dOpOjZuArydSdASupCQwRPVja+m 8PQSaAdThTifIBbP0Y2TeeA3yfGbHubo scMezJAXr7FGDQ8XIfyUYBDBoDaINi6j V/oI+aSnwm0uWdxHqfonzioTYenuTsax OGLYYuCMn9soxIuXatO53MpqfsJgh7UK FDCDGKA9pFts1vTQdCMPGgEsuYHjsYF0 /kUoRAQJM3jh0t3wE3JQ/HjFIyR37NCM w+i20b1v6/Xo86gy2nhXq2BopawLPEh0 Fv2UCCI7f8k2JvvF2o9udq/N+FE=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcgeCFZiwMD0S6TVgSK3Ob/MKFo+ dHYeNgoOjxH3JoxV09WShBXID/LwKs/e sYAIzTOBB8Fu9IDoVuQe5prOEcGZRp6u zF7JLnqrgOzoaSPRmKsvXh8DfipA56qE AqNwYBCqzlbGIGjHOQ8t+Xonp4fSBYbN MLmh7o8+u55k7PxemKTCiAnY7Bjx0g3m J1HuFvQXDDdvyletuoS3NKqrkjqdOHRB 3p3GKrKgw+zVb05UEyotCEKjqqi+BOmg cm8rmUIeaNQREjxrUyr8/Ry67h5NJLfq LAqAnSePf1PyhQcl8XjGHXN7w1tLLyJu Gy5B3zLSW1P6jIjon77PSn+cXi0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAfX4eSO5BEPXggvx4jL5HkEjbA+5 QD8Acnh4wOHNv/OJX4QhKnpoMDOrpp38 n0Rgcr8qZ0XUqiJWl8eEH//wrQDHoO5/ JHRmibVrcA6UqfhUa25D1BfeMtTgMCSS +W3/heB/YhyXOJtOdWDmJutDY1nfJUSK rF6XwBj4us7U5jvwXwq+l36AeI4q7I87 Std0GNgRdA6/cJtApPoGv/0oNE0iNTyu IBcAR+AeWu32+JavhTycmtMfZ+KOIShq uI9RHzChZ9yLikAT+gclJGLLlxIr6HDr SLChCG90/yvmc57dLn8qhRWRJtJeLT2V HSxeP7oREb6RSdkxET6OTf9GYHE=
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20170801000000 20170711000000 1867 . FyApZOPhk7K90BGdNryD4EbpD/0bB6th 8rztEnRZV8dmtILZOaFmkoTweyJGpZZU HoVjsffD+wr1FlngJy7mYwZRIctK6NYJ 3aocw1BXNeSZdvmaQnPOI2TBbHhb8bhG GHzpsmFMNmD5lMDMbjZWzJKfv5pCboun pbyowlGNz9uX/x2uSGkTa0m4XkCEQLYT lHLntHu07Y834yfmcIUsQC2Z0ZWTdumh /RXWrw4dwTPT8+ldiUOcVwGsjaKGkyq2 +qCohuU7wrjJoWFTqZg2kbCfSP+4/kLk x7/AetbXAXyZaFRofo/3Lxuz+5lUs/p6 NoLBniZG6mxR5t7B4brVsg==
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20170801000000 20170711000000 37284 . wDk28IaSAi/tIgARka5b8CyyC8fcj092 GMhS6wFy7M1rBfuALvcD+Tx/PpCmWFiq bv5zmomvWzMfV80fYGngc2zOpuQfrSfM GN0JnR2PVh84B1x6H1jpnnxE4Q3fYNBi jNHRwAuuACAaSv98vcNt964v8eZypCrS t+yLz5AQUMciG0PNP15UbgF9gyWGfIW4 dgGOT8R23vxPdaXcRMltF6lW9IUTU46i 4gC345GYwrJw5BW1cVLg5YL3VtEUFJFM K0A21HwJgm2IH26AHvyuxOnOlwSO6AXu Cxg983nxABMz0ek6hXAFYkMAT2yINUrO 2nQisL+40gnq3hEUoNZUKA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20170725000000 20170711000000 37284 . H8r2rvoxyCYAlajIv4puL97JwYg141+F uqXAchdx9Ko0P/7ynaRVttOIAQ1WtCha REXryEctxIp+sfOTzZBJSfQklRrYZuwh TWl9w7Ly1HcSu4NJG30Tg0rfKpPLHbsP dYee6tuJgqK+ZYU06bVd4ZySZ7WHLBMh gf3kjk2QbArp/+wtvbzhELXEw2fj2SD7 GKjvifUa3EymG7NyzFtd6+eJX7/88r8V qa176g7ueukqjnt2Yyiw+mKbYAsr3Ob3 Yx+x8tWUF6/depiB33jNfxhQJBD824kz c3o+hKWI/Dq+9WNRjm/hSGZZqtjdpdNa WF1Iq25nMg/G7oWezFIS+g==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20170725000000 20170711000000 37284 . KJYdNDfdqXwtTQrU7Rwe0mN5mG90GfSm Uu7w10wZhFr/Vi0yXdbVbs3yBOmlPMEI vQ3TFjfMfdyNGxBxoJ4gX4w47GIXXX1W uIN6g+xt0pIfYo/+aKY8776akfRjBNOk Un825x9HArLiA64QkOZya1ESWFKzx9uL dIwxHm53QkmtAPU0D12xrZmamdLKANQu rHUDDxZYg49sraLDZQxTIP+jSA7pge9K 5iNdCs9RuJfW43J1hpaM7tNjq34w9Je8 LnLEUDsN0VD2w375wXEznwx3CQk2lpLP hjTHHp/fs51GwAjVD2v8GHYOi+3CBjkK 1c70SE9ARujlpDzybbGv3Q==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170725000000 20170711000000 37284 . QQQI7Bnp7H3UXzhlybA1UfJxyUmtCFO/ Syv/GVoQfmHuCpTye9COUaqwC1d6H9+K 59L7zKMZ8Vpn9OMmuYD/w47qwPLR/0LK Ni+AuHqi4Yjv8ADSqOXzXNl+isWWvOJd 4hR11ZuRoZW5tgWu5o3Ih0Ec5Y/VPpLj JtKBRTBk5xccSgGIk8GSovu7hyMEKamI wMIO1rIfIKSYFrp09YCIc66nrU6/hyg2 0tUtsZNjZwg9c2O/NIH8VYSATw0ppFnO LMoR6CpRZyCrxCB1FsrnyVuzRTYvAdO6 +7UuDiliPQ4GPHFb1wdhTr/W+TGh0UIQ JI02FsITRsf2BNjE6DXk6Q==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20170725000000 20170711000000 37284 . 2OY96fDuZ/ZCQxiryCYeSknlLFG3btVH W15h88UgKw5z0eLmBMUO95tPlSjF5A/Z PgQT6+qXXJMoLNxPGeTsNGl+oPxXCeFq 7+egtR1KLNafoYb8TgpW9I/lsKi8KwY9 tL1ySvoultD8Vd4RYx5MD/T+NI1f5rk7 y7UxPngi5zIw+GMZnGWSdJW3qhevUGLN l5wFjFhSleLXO1JkCzM+fxQJgR+qLJk0 0YfFK6TCEPwMBybwWN6/NuRWotfc57au 7ZavTEPDDLpWivElJyMH1/BSUviHbJI5 brQsdc91jADXp+W9lCpwNDNs5iU5WdJv rN8tqpp/MHJedmYMW67jdg==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20170725000000 20170711000000 37284 . v5dTMrchkwFBx30aWw+I3IrZB/rH79Ar zfnX337y5PlocwWGl31QXxl8JXjt14wU zRTGebGceKeYpuSl4KcR/zxPaP/Y/wtf o9HnxsuAja9vlQh2b2nY5TWRHf4ZqCkA c41SPAAn59O2HKEE4/9TPn1W47n4IZsJ 4mb/x7G6pL7jUykaVnLtlnEv3RrQxxbp hJe+JW2bk8Gj8ih+fYavO23pxUIR5vEb CSM9yAFJ+is4X6d6UqCOgvy+qHzHcTAu 6PJzmI1fX2mNkn0Zj//DaQg+cRkggEsE FJGSxvZBb7pvOIy7khMNB+NuwSHkcGyp OVplGjchbkLY59kOYKRQNA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20170725000000 20170711000000 37284 . KJYdNDfdqXwtTQrU7Rwe0mN5mG90GfSm Uu7w10wZhFr/Vi0yXdbVbs3yBOmlPMEI vQ3TFjfMfdyNGxBxoJ4gX4w47GIXXX1W uIN6g+xt0pIfYo/+aKY8776akfRjBNOk Un825x9HArLiA64QkOZya1ESWFKzx9uL dIwxHm53QkmtAPU0D12xrZmamdLKANQu rHUDDxZYg49sraLDZQxTIP+jSA7pge9K 5iNdCs9RuJfW43J1hpaM7tNjq34w9Je8 LnLEUDsN0VD2w375wXEznwx3CQk2lpLP hjTHHp/fs51GwAjVD2v8GHYOi+3CBjkK 1c70SE9ARujlpDzybbGv3Q==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170725000000 20170711000000 37284 . QQQI7Bnp7H3UXzhlybA1UfJxyUmtCFO/ Syv/GVoQfmHuCpTye9COUaqwC1d6H9+K 59L7zKMZ8Vpn9OMmuYD/w47qwPLR/0LK Ni+AuHqi4Yjv8ADSqOXzXNl+isWWvOJd 4hR11ZuRoZW5tgWu5o3Ih0Ec5Y/VPpLj JtKBRTBk5xccSgGIk8GSovu7hyMEKamI wMIO1rIfIKSYFrp09YCIc66nrU6/hyg2 0tUtsZNjZwg9c2O/NIH8VYSATw0ppFnO LMoR6CpRZyCrxCB1FsrnyVuzRTYvAdO6 +7UuDiliPQ4GPHFb1wdhTr/W+TGh0UIQ JI02FsITRsf2BNjE6DXk6Q==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20170725000000 20170711000000 37284 . YryqMdq2kUnFyGH+m2iUSdQDu2W2W/Oc tWia7RSvPKzL+W9QdxQi3D/HX3/jXEXj vsIRJQiupIkKOLwXcugV0qdAK04pEnWp NgqKKmNzsjzp+CPy5VfFrfWuZ/GelsSh NbD2lPaNFZ/PBVk5dlyJJJerG+CiT0w8 l+uoxdwQ88OpNoXf+vTchnFBJmcuKkW0 +eXOM8R0nt1lt9pQxycxExiJgw2wEv64 vVPdDCytzJ43XTwKlpjPaX4j5Ga13N6K kLf/wltyB0ucLe+ERFaHuQBEs2buQ7zm UF6l7LfjAlepU1UwE7Zk8dXrKbWmD7oP kXx/6U6E51RRyB8w66qsWA==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20170721000000 20170730999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20170804000000 20170721000000 37284 . St+fTzqEpu/Sq4oQwNZY9YaEkD4+Oqgy LFW4JNxYuwM0vMZnhAgMxrHZKI4Vg+I2 +85inalP5mYJm0kEYm7QF4FeRk1xWKWb f8GRIYSo3eWsd4FHlfGtLkLm9MIcopzY 4N6v/tHPVanhT61ppqXPg9A8DyjPrDKp tw/aMyV7ewV8GFpip/YHT5imMChbyfyM Gg74MhqREpO+LpCFeXseH4O+1d4gw2nI ewX7sEnb5CqZlKHf/eas5MpwyUysZ0y+ UPerUDPc7NDo/zZkA032BeqMFXAcwdy8 vN+UANTJHWFLhnPtCHX0Imc6rW9Zlopo TqCrXW25bMLByViigbZwIQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAe1udmhwaWyg6PD00YReHeT6nYeN hKAy6oKMc7X5NXXJq6baVZT5p3AnFm7B 0ReT1dOpOjZuArydSdASupCQwRPVja+m 8PQSaAdThTifIBbP0Y2TeeA3yfGbHubo scMezJAXr7FGDQ8XIfyUYBDBoDaINi6j V/oI+aSnwm0uWdxHqfonzioTYenuTsax OGLYYuCMn9soxIuXatO53MpqfsJgh7UK FDCDGKA9pFts1vTQdCMPGgEsuYHjsYF0 /kUoRAQJM3jh0t3wE3JQ/HjFIyR37NCM w+i20b1v6/Xo86gy2nhXq2BopawLPEh0 Fv2UCCI7f8k2JvvF2o9udq/N+FE=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcgeCFZiwMD0S6TVgSK3Ob/MKFo+ dHYeNgoOjxH3JoxV09WShBXID/LwKs/e sYAIzTOBB8Fu9IDoVuQe5prOEcGZRp6u zF7JLnqrgOzoaSPRmKsvXh8DfipA56qE AqNwYBCqzlbGIGjHOQ8t+Xonp4fSBYbN MLmh7o8+u55k7PxemKTCiAnY7Bjx0g3m J1HuFvQXDDdvyletuoS3NKqrkjqdOHRB 3p3GKrKgw+zVb05UEyotCEKjqqi+BOmg cm8rmUIeaNQREjxrUyr8/Ry67h5NJLfq LAqAnSePf1PyhQcl8XjGHXN7w1tLLyJu Gy5B3zLSW1P6jIjon77PSn+cXi0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAfX4eSO5BEPXggvx4jL5HkEjbA+5 QD8Acnh4wOHNv/OJX4QhKnpoMDOrpp38 n0Rgcr8qZ0XUqiJWl8eEH//wrQDHoO5/ JHRmibVrcA6UqfhUa25D1BfeMtTgMCSS +W3/heB/YhyXOJtOdWDmJutDY1nfJUSK rF6XwBj4us7U5jvwXwq+l36AeI4q7I87 Std0GNgRdA6/cJtApPoGv/0oNE0iNTyu IBcAR+AeWu32+JavhTycmtMfZ+KOIShq uI9RHzChZ9yLikAT+gclJGLLlxIr6HDr SLChCG90/yvmc57dLn8qhRWRJtJeLT2V HSxeP7oREb6RSdkxET6OTf9GYHE=
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20170811000000 20170721000000 1867 . Xy4GIqglfhPKFoO7bbK/FzQwbWYw/e/d MwYYHGzjvui3MykT53WTP0qQk08Pr+Ae zF8Z05Bv4j7QTY3+l0W1NVMQD1akc0Tp purQRzqgv/PzoTzMm0g7h2mGHP3R17pV kWAb7tv1ha6oO6cx8TQfMeKfuI0Z9vPd N20BYUd1FwLLITnbXrpCRvNMJw7mf3fN 1SbTJrMquQhcN6EA3pdDxYGktabYBnRv DCeWluSHTBlqym0Hq3afphc0zYwfwgUq ZPKcuxXjXbxO0yZCeJjQ49u7z55nw6yc 1qSJEjPtQPKHjcJSdbB60rNIdg5osKqz iApC63Ug8VTP18eIugRSjA==
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20170811000000 20170721000000 37284 . S2cOcWa3/a/y5udmWgsXal3a0SlnUpnV Wr7eJCdkQ8Au4V+nOdaBj0ZFg0RscC/I QHrNoKaY2H0QpuLtX+wtav/CyL36Uhse xP4MzY+KPJkPzCq8AsReHeFWFV1PSiAo 6PlF81QgLeO5AytRu9dEPhgOc0k9q7cQ np+STJqoD7+j0C8rKclJmAa4qsrFgIk4 45pgwgbHhmbnWHCpKLJXK326K1x9yP1W DZIwX9U+UEDyZxvdoiwrXcytehvae6Fs FdQ1WlP+SLdrQC4NrQUeTHBlveYlL0o2 gW9+M/lljq+Tb+3T2eoBRCev+CW4E9jc UEV+v0rGHW7Oqivcb9yoiA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20170804000000 20170721000000 37284 . CRMahktbnKGSnjLeg1KOUHLql++CLK7C A3v6y6h04COVZcq7IojE/woy1yOGY2cK 1oyFvhvYn0fN9DTmsGnoNojY4kiyOvg0 egbizLiHHsy2QJTM5sQOKy2L7me5LDjO eGlgVAOAs531ITPnGXM0MGO9yz0Oib0p ShQ+HQVUzHGSFYj9VCWLodPKuMYm6wPJ gkpzIT3Vd2QkHvMOc4tW07FQMNpNGUsm 369fnbkHcHiwjw/uKoZdPV9XQJvlQNsz xgF99hhth2Ix2zDYFYP6X6D9uIjD4KC1 Mpr6AFEq/Gp/DJt5soQNrO3z3D1qfxBy NoraHiRPzZ5HXh9Zlg7pDw==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20170804000000 20170721000000 37284 . 2SPmEVXrEp4xGnYvrk6r1Mz4NKC5t9E4 nV3NA9fWQRkld7URF4ryKA2y6GxlYHig nY9guQ7diD9jV8oQUL/9d/wcPIzqGvWC Q32LTki3BTwky25kNIByRKiXrSEpNMLt 1eaonaNhb0BFSoZGw4l7A34TJ/3JjM35 1R/F/PdEyrGE1Yme3xb8YgfQQuOS8Ymq 09iWA8zOI9vw403Ksk1t6739Mtc+j7Ht lxGtA+Nb4Y+6jZc2bK3TzGgkmdxfaYYM Zs+rIhS2OFE2tmAsHK/A1VBTuEc4m/OM X6UOWZDPavDmFgYjrfPn/fQ/v1WLgcJu NpI049g5FAKLuzd8qjvKoA==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170804000000 20170721000000 37284 . XSyzOTwPB9zTlOCKdDm52NyuSsLoOxTw owOD072MFxbGXautWxnnDUmKGr2+lWe9 hmXtSOQCxDtL1fQGhdPZAqwL9lSoWUEX t5SMJ9muqI7KzPiwXHAcyXJJ3xxh5i60 UD+8EF5pcgwhTtzcrRgKwRizDpA1b3gv 3kWO49DFOh6wkqj1NUfUCSNgvG0nMta1 y52vpElqD81dPdrd6DZ9a5QCdbuyuLSN w0825JVj5GK1chdS8wp7PiPYLCrndAHl Tzm9o4Oejfi7jeszPZFhze/2NsQeADDc tzDD9fVXPI63pM721DbJXZCAsg7ekyW0 BOSUd2xSWgZ9WPP8xDE8yA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20170804000000 20170721000000 37284 . St+fTzqEpu/Sq4oQwNZY9YaEkD4+Oqgy LFW4JNxYuwM0vMZnhAgMxrHZKI4Vg+I2 +85inalP5mYJm0kEYm7QF4FeRk1xWKWb f8GRIYSo3eWsd4FHlfGtLkLm9MIcopzY 4N6v/tHPVanhT61ppqXPg9A8DyjPrDKp tw/aMyV7ewV8GFpip/YHT5imMChbyfyM Gg74MhqREpO+LpCFeXseH4O+1d4gw2nI ewX7sEnb5CqZlKHf/eas5MpwyUysZ0y+ UPerUDPc7NDo/zZkA032BeqMFXAcwdy8 vN+UANTJHWFLhnPtCHX0Imc6rW9Zlopo TqCrXW25bMLByViigbZwIQ==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20170804000000 20170721000000 37284 . VkUQMiyde/Q7I4g+cgTWu13y0RT4VWlu Rf+/4CZu+huIDyhpsKNggG0Y7JEqKo9p FHDds//kvFsMQuhRTUvt/Jrnxwig+p1C RwQ9tYbZIkWCI1wOCQ/mjB1F+8iq4uOM OdGh86xtiCaWWDUPkZr5Bi+AQqA9mU3p ksaoI+fWs8rjTNr1SOH9Hp8s+hxBAPJs DRXrAGcpIMbmfr5pPJPtX0D+qDW2m8eA Vopl0YLyMkK7dj1PEV/zGvv222oht//0 mXFVse7ceaI+izSggDsr23CI3tCdAhSO RiEQMa/XeDWv7OLXIy5KEsjgf9DGL1bI SdX2U7DNB0ZdSY9VzUpCRA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20170804000000 20170721000000 37284 . 2SPmEVXrEp4xGnYvrk6r1Mz4NKC5t9E4 nV3NA9fWQRkld7URF4ryKA2y6GxlYHig nY9guQ7diD9jV8oQUL/9d/wcPIzqGvWC Q32LTki3BTwky25kNIByRKiXrSEpNMLt 1eaonaNhb0BFSoZGw4l7A34TJ/3JjM35 1R/F/PdEyrGE1Yme3xb8YgfQQuOS8Ymq 09iWA8zOI9vw403Ksk1t6739Mtc+j7Ht lxGtA+Nb4Y+6jZc2bK3TzGgkmdxfaYYM Zs+rIhS2OFE2tmAsHK/A1VBTuEc4m/OM X6UOWZDPavDmFgYjrfPn/fQ/v1WLgcJu NpI049g5FAKLuzd8qjvKoA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170804000000 20170721000000 37284 . XSyzOTwPB9zTlOCKdDm52NyuSsLoOxTw owOD072MFxbGXautWxnnDUmKGr2+lWe9 hmXtSOQCxDtL1fQGhdPZAqwL9lSoWUEX t5SMJ9muqI7KzPiwXHAcyXJJ3xxh5i60 UD+8EF5pcgwhTtzcrRgKwRizDpA1b3gv 3kWO49DFOh6wkqj1NUfUCSNgvG0nMta1 y52vpElqD81dPdrd6DZ9a5QCdbuyuLSN w0825JVj5GK1chdS8wp7PiPYLCrndAHl Tzm9o4Oejfi7jeszPZFhze/2NsQeADDc tzDD9fVXPI63pM721DbJXZCAsg7ekyW0 BOSUd2xSWgZ9WPP8xDE8yA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20170804000000 20170721000000 37284 . yz3cwQjabEPPuuTjJwVW5ORb/PiJsB/6 2TEvBkpRyLvQUHr6Q6d4HosmIEExmrdq N/iNj6SiZ0upa4SyftzRbyYcoGeAWonV yha4FxCSy7T/0E1FlBNCfrdjCKoMJtOD rmzJmVRA63HbxcSyhYLSB+sN8LBhoLve iQkMJU9iBNV59qvoztgUnPi+Ky5y24vN S4Bg7oCgqWvA4L3JKAvcek3fymPj9/2q E9jPn7i2jtTJmAVH1cNKHX1i5YCVuNK3 oXHD22HJ3wxCumT2c2GWepx71dvrgjGv 5a53iSSOQ9aVzD5JSwf5rSytKPGdb98e Y2jkgCXYTedrm0UctRktNg==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20170731000000 20170809999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20170814000000 20170731000000 37284 . xmUjSrpl9mlXOv44w7/rSXtlhoZeVVLU o96YJ2RoSa3OStDW6NrvsC5xG2TW+Ow0 Z+DU2y5G1hGLQkcT6KXbEoGAHBGOXI4M ZmgjLxD6hf5ttlTEZMZY7+y/wXzZnNMZ OJTCYkVht1+uZkI3GWtETmxNvLbNnRQt TSjrp20i692OmUi61utqEBR/wyiZj8Qz iCG4FvSWETf/XrjjuKpD7EWfn30RWnmE iB5Se/FbGtPVsHgPLsEiDLEMMRVjqgGh GGZQg9UVZmyHY+h2iTVryB8DK0IINNDs doIvs2ywFLTC2LhHVTmPvz2p9KFR4RIc 4Ci2Mz09N8D9umQRIekU2w==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAe1udmhwaWyg6PD00YReHeT6nYeN hKAy6oKMc7X5NXXJq6baVZT5p3AnFm7B 0ReT1dOpOjZuArydSdASupCQwRPVja+m 8PQSaAdThTifIBbP0Y2TeeA3yfGbHubo scMezJAXr7FGDQ8XIfyUYBDBoDaINi6j V/oI+aSnwm0uWdxHqfonzioTYenuTsax OGLYYuCMn9soxIuXatO53MpqfsJgh7UK FDCDGKA9pFts1vTQdCMPGgEsuYHjsYF0 /kUoRAQJM3jh0t3wE3JQ/HjFIyR37NCM w+i20b1v6/Xo86gy2nhXq2BopawLPEh0 Fv2UCCI7f8k2JvvF2o9udq/N+FE=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcgeCFZiwMD0S6TVgSK3Ob/MKFo+ dHYeNgoOjxH3JoxV09WShBXID/LwKs/e sYAIzTOBB8Fu9IDoVuQe5prOEcGZRp6u zF7JLnqrgOzoaSPRmKsvXh8DfipA56qE AqNwYBCqzlbGIGjHOQ8t+Xonp4fSBYbN MLmh7o8+u55k7PxemKTCiAnY7Bjx0g3m J1HuFvQXDDdvyletuoS3NKqrkjqdOHRB 3p3GKrKgw+zVb05UEyotCEKjqqi+BOmg cm8rmUIeaNQREjxrUyr8/Ry67h5NJLfq LAqAnSePf1PyhQcl8XjGHXN7w1tLLyJu Gy5B3zLSW1P6jIjon77PSn+cXi0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAfX4eSO5BEPXggvx4jL5HkEjbA+5 QD8Acnh4wOHNv/OJX4QhKnpoMDOrpp38 n0Rgcr8qZ0XUqiJWl8eEH//wrQDHoO5/ JHRmibVrcA6UqfhUa25D1BfeMtTgMCSS +W3/heB/YhyXOJtOdWDmJutDY1nfJUSK rF6XwBj4us7U5jvwXwq+l36AeI4q7I87 Std0GNgRdA6/cJtApPoGv/0oNE0iNTyu IBcAR+AeWu32+JavhTycmtMfZ+KOIShq uI9RHzChZ9yLikAT+gclJGLLlxIr6HDr SLChCG90/yvmc57dLn8qhRWRJtJeLT2V HSxeP7oREb6RSdkxET6OTf9GYHE=
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20170821000000 20170731000000 1867 . Z3qb2ZevT0tfFrjLk+zVHYcEG7uU/lWD DMVZydBTABQqSEzRee/neEeme5MU/V1e D5nrYf6XJVqsj3O781vB/0OYiqbPcxgU m1tqelVDH0NcBSB0UIaADl4TqA72OnNR kBUa9U0pBJ/4e70rubuxieO6WuyPRzti UGaydhR8CREMr+r8FAItdjJWPFukIvrt ju+brh6xZKl5Xuxw/dklaZp+Dd6u2jRW 9tH79rh5N8rTdHdxHjn3JQs02wzx4iD4 OHd70QOW6avgvg2x2RnzS4Q7n1iU8hcR aYgvk0+FToMFlxhFW82M3xRDCjTrktF+ bUKyxz/frYKuS17pxRS1gw==
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20170821000000 20170731000000 37284 . gF++btbXlsLxe02aeytRLug6tJN994B/ F9LigfvZGjC/GE47VIlUmS8qgPWA4Sqd yUHiforLg5aMLO/CMe5WxqmEzSoU03Ka +PXW2R+q5k6tsTuowVDvM6W8P72f4epk XwEK27dmVJXeTbYVoREV186Sk0fowavm Hqdo8AX2XXB09pkBto8kgjH9otXnB3jA 2BRZKNicP3wJoH6S9Oy+nRSzyWRyBDjX Apv2h+quzWBNYux1GePJol5V/wem176Q kHUolQtCcRB4XIJG0BOHm+6P5nHRZ7iU JqsQJJ5PnnzH4sBO1ZBSJgDCzvIb1bLb Ixd3VGg3ZrEjrWda/OR8ww==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20170814000000 20170731000000 37284 . tGGr2W4HHWtFIdziLL0066dn1UKLSfv0 L0l8OyvX9fDPUNgKtVRMo91kNMm2Mp7o /pHiEDL8ZxxGsm1WKBmANjIQ2BBKzb0S TXmRrMmeuGtMiI+8PGQ4ZnlU32PtePyC /6WNh1+5PIg6cPkMJPx0+h7Mj7mdtKDi We2Ha56Fm9lMuEo+igAmO4rKLSrkjqVS QH1xwTwie+KPBHTK7rIannW96KYfzAEI ybN+CGTCgPawXuYxKcmiq2ZmNSoU4bJp YkZdVoFPr4btnO1Ai1Mh2QhcPlnDuN0M OWNzlJ7DaObEK7oCHMkb5tvBNTu9CsGO SZvGA603Md2D0YZfRKpQ1Q==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20170814000000 20170731000000 37284 . ykmbNQ7VSbpkumK9V7pECrxiws53YIKM O+OaKde/OHaQCVPIXgbVf3fc+Rk5e630 zHxZdwwpOcVJKr9RSCLylRnZY6ocDySy 10qeqVV7QCgwYN60/68DAPz2WjIQWGN4 UH8mtr+KyCa0Fj8SOzhPf9VBZ4u5w+1z lOLhWqc+lmelm8VjgWbyo6dn+/IokwKU Ks37sTh3sa+qk9zREwv0Ut6YEo//DOcT WdXxt1+MvmM0jssuWaCGqHiEQZlnVmJ5 7puQIiPUCj+YMEbX+rZB5CnP4eA14FD3 /L/Nq8cwb+0+yd0BjNS5bHfE1eIQawbL e7LrztJSYeNvS9TIZzRKww==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170814000000 20170731000000 37284 . PY5wleVbShlse11/pOPJMYiFBsNnWfE1 dUcPrAnLEZE7fTF5uFF/mAdkfLLIMG0Z mx4d63ilbMWJXBwmSk5nDsaDxbYLjqGW 7kitDATwiNUKtP3MXZ3jPl7qIq4GjlHn Hm4sRdsuo5OXO28mHCBhKVUCgjK9+Tg1 nxPqhc0V7momil5Tpl6I/h+gX6saMOsb 2wgcVV0wHwI2dOztgNOpqgQda3VqHVRd LN+sq1k1bDeZt4YdIonPwhPY45tZ44VH VPpjbJcyhJSzvQte6gCrARK9/MUx/1sJ bdP1o+cXJxzOn7TgfETYUS9QCP+7pbpL 55s1uno8nV88D5dMAuCI2Q==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20170814000000 20170731000000 37284 . xmUjSrpl9mlXOv44w7/rSXtlhoZeVVLU o96YJ2RoSa3OStDW6NrvsC5xG2TW+Ow0 Z+DU2y5G1hGLQkcT6KXbEoGAHBGOXI4M ZmgjLxD6hf5ttlTEZMZY7+y/wXzZnNMZ OJTCYkVht1+uZkI3GWtETmxNvLbNnRQt TSjrp20i692OmUi61utqEBR/wyiZj8Qz iCG4FvSWETf/XrjjuKpD7EWfn30RWnmE iB5Se/FbGtPVsHgPLsEiDLEMMRVjqgGh GGZQg9UVZmyHY+h2iTVryB8DK0IINNDs doIvs2ywFLTC2LhHVTmPvz2p9KFR4RIc 4Ci2Mz09N8D9umQRIekU2w==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20170814000000 20170731000000 37284 . hKhEcL2qwpjPZNsyTLnqTytfuTgEyHeN k3N+xhhy8nFWu1CiQLcdrsSUlG58h4I0 eFQw7bwZR6anymWx3nJGQe1UnH0ugbcd UlP2JstzqYuMa4+mpaA5IMzKe8vflEYl nas1j2I3tq59qXnRq+/XLAE22Lm2IDY6 Np/3E9zRT8plr+baWcY/E7E1xJcyDY+v id7gXwQa3EQxvOIKj0Q17xyJ2g1t/7SY vvJUDmEHZ/OKknqgdzwskAzUreld41qm LILE3TH8bfWGC25JSKxR9qU7c6sU0oGs 6pR7//IpTWRUH+tskFVmY8Jfx/ot11+H +mPqViBvNhXl2l20HJ635Q==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20170814000000 20170731000000 37284 . ykmbNQ7VSbpkumK9V7pECrxiws53YIKM O+OaKde/OHaQCVPIXgbVf3fc+Rk5e630 zHxZdwwpOcVJKr9RSCLylRnZY6ocDySy 10qeqVV7QCgwYN60/68DAPz2WjIQWGN4 UH8mtr+KyCa0Fj8SOzhPf9VBZ4u5w+1z lOLhWqc+lmelm8VjgWbyo6dn+/IokwKU Ks37sTh3sa+qk9zREwv0Ut6YEo//DOcT WdXxt1+MvmM0jssuWaCGqHiEQZlnVmJ5 7puQIiPUCj+YMEbX+rZB5CnP4eA14FD3 /L/Nq8cwb+0+yd0BjNS5bHfE1eIQawbL e7LrztJSYeNvS9TIZzRKww==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170814000000 20170731000000 37284 . PY5wleVbShlse11/pOPJMYiFBsNnWfE1 dUcPrAnLEZE7fTF5uFF/mAdkfLLIMG0Z mx4d63ilbMWJXBwmSk5nDsaDxbYLjqGW 7kitDATwiNUKtP3MXZ3jPl7qIq4GjlHn Hm4sRdsuo5OXO28mHCBhKVUCgjK9+Tg1 nxPqhc0V7momil5Tpl6I/h+gX6saMOsb 2wgcVV0wHwI2dOztgNOpqgQda3VqHVRd LN+sq1k1bDeZt4YdIonPwhPY45tZ44VH VPpjbJcyhJSzvQte6gCrARK9/MUx/1sJ bdP1o+cXJxzOn7TgfETYUS9QCP+7pbpL 55s1uno8nV88D5dMAuCI2Q==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20170814000000 20170731000000 37284 . y+EKJ2utPXopOSD29GZvwZOAx7ICHszl ia8LbeT6jAUF9oTVm0YKC30VQULQfskc oWwPssD9ZsGLUi/8GOsE4cropD5lk/AD Df021TAv/tgQTvIvNggpu4u4fpOopxz4 1E9P/rf7nROayVTo2dsgIg+zabkhb96i yIr/nhoxkvEYFcYTucfZTlVhOwjYGqvr FE/Ro4Gl4axf5FN+CK8CNebd43Ep3yJ/ 3UvecFYMofMEytguBLNsHFIrOtKGLQfr YfttDOl9Dr2h9txDAVXIWrdvYEuLnYR9 I7vBBfgBMwkyDfhmG90xFt51E8KSDcgQ lDMMiPrnLTBjIa3d21Bt6w==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20170810000000 20170819999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20170824000000 20170810000000 37284 . E3PCc79NFLHFfnf2+WYF1PvcIPYEF7cf CghVd0wWcP5B1Bh+vEdo6gsJaYD9/QtM q68IW4bPm49dI0zRvkRGG4NjE8zU8bO2 wGf3Ky61F4Xm1fwq9TYYCT29ofkm0NpE 12fP5P/ahA6K7cw7BfDPjLPhlqw+/iwO IlEKWbeg6CT33eMBfhQC1B/47bLon/AU 4AAd+sy50sMhkTeId7Zy5BAHg6V09Qg/ FV8PlgiSUgmSiEa22gITJOEu2LtRYPFe cJHEGZAtKAt30sQ0o5dR6m48mWdOo/4B GFoC1HfrpsCyGtpi9Ab3jFjwtpGFqu7X wzQS8OlqBPFYA33OJq4f+A==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAe1udmhwaWyg6PD00YReHeT6nYeN hKAy6oKMc7X5NXXJq6baVZT5p3AnFm7B 0ReT1dOpOjZuArydSdASupCQwRPVja+m 8PQSaAdThTifIBbP0Y2TeeA3yfGbHubo scMezJAXr7FGDQ8XIfyUYBDBoDaINi6j V/oI+aSnwm0uWdxHqfonzioTYenuTsax OGLYYuCMn9soxIuXatO53MpqfsJgh7UK FDCDGKA9pFts1vTQdCMPGgEsuYHjsYF0 /kUoRAQJM3jh0t3wE3JQ/HjFIyR37NCM w+i20b1v6/Xo86gy2nhXq2BopawLPEh0 Fv2UCCI7f8k2JvvF2o9udq/N+FE=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcgeCFZiwMD0S6TVgSK3Ob/MKFo+ dHYeNgoOjxH3JoxV09WShBXID/LwKs/e sYAIzTOBB8Fu9IDoVuQe5prOEcGZRp6u zF7JLnqrgOzoaSPRmKsvXh8DfipA56qE AqNwYBCqzlbGIGjHOQ8t+Xonp4fSBYbN MLmh7o8+u55k7PxemKTCiAnY7Bjx0g3m J1HuFvQXDDdvyletuoS3NKqrkjqdOHRB 3p3GKrKgw+zVb05UEyotCEKjqqi+BOmg cm8rmUIeaNQREjxrUyr8/Ry67h5NJLfq LAqAnSePf1PyhQcl8XjGHXN7w1tLLyJu Gy5B3zLSW1P6jIjon77PSn+cXi0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAfX4eSO5BEPXggvx4jL5HkEjbA+5 QD8Acnh4wOHNv/OJX4QhKnpoMDOrpp38 n0Rgcr8qZ0XUqiJWl8eEH//wrQDHoO5/ JHRmibVrcA6UqfhUa25D1BfeMtTgMCSS +W3/heB/YhyXOJtOdWDmJutDY1nfJUSK rF6XwBj4us7U5jvwXwq+l36AeI4q7I87 Std0GNgRdA6/cJtApPoGv/0oNE0iNTyu IBcAR+AeWu32+JavhTycmtMfZ+KOIShq uI9RHzChZ9yLikAT+gclJGLLlxIr6HDr SLChCG90/yvmc57dLn8qhRWRJtJeLT2V HSxeP7oREb6RSdkxET6OTf9GYHE=
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20170831000000 20170810000000 1867 . NvCmbFb31NahFemdIPwno85Uzjwj3QbB bdo1NsGTIMU9IODLqRDBR/6i5XFoqOWE +SS0W8urCtb+dwg8Z0iJD3isVGOzDtnG tjoZq19S3E6UU28MehpOh9u6dJoZqlSr 0jvinZsVSKtd784O4IHBFDLhaMtGsTab PIEc/jY3XeHSQu3a0O1SWZaYy9/tn89v mWdHsYNmyR1EC6JhXf5pb3yPjgHhUg9v 5tS7poOWpYJMkLqNfIWKyciCLpc7IW/g kvuE+GI89qBVfFBQmhyKINHHIouwexqC qdTqrEL2VKPyqsiWjJ2jT9JZW/K2zvgH /WoCneDpHokp04wS1dQwpg==
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20170831000000 20170810000000 37284 . Igc1ImQmud5Oj4KrdnY6fFBJKK5T7DF0 lULf/IsYtNruJUmP9h/5JNaqZTfhCrbH SOFwlm01qeuZb39X91FMZbCegRowGL+n 3DTzwl6w6USRd4MhVVtTJjvP4+lB8rwm Xkw0k1sWK3RqnJ9vmINhKu/HHxLGG+qT VgjtMJ27DmIum6IPt53c8waqbVnbNzL4 LThPEEEzt1gMv+FKmMlIQw6GbdFrYatD JfXAu0+B9UhgZtkPzjq5sMgvkmKonmHp qXuuRnbzxehv+Wq09cBQwu9xHfnJxGHy i1vsVGIxPttLwYhGxnOvzOJEylmlOCLP 3PCXFuu3B5l6b5LFM7ssxg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20170824000000 20170810000000 37284 . LmgEtpDF4tZPcrrnqdFJHNay9PUwVupw xLjeDIZyzX1LTtrbHfiAS/luCAvHcZGh h9WJkxfulM5nthpxWFu/qEiBljjBXTKr YUAoCnV05VLjBjjrV473O197CwPCMWzh RE4v0Z+0nenW+zi4ckTT66h88UjGzq65 uHnjmp2X+0M7R+SHY3JhYalFo4Lo9PrW 26gnbDTvNRbV+XMLD0YS3YWIZdMRaEWG 23f39ZrCZF1P8AP7pbsfRjchQJz6ApIl KWj9Uyl9oXL434ClQGArKnXrX3eMVnKl 0I4uVUK4ILF9oz/ckxBPPzJRl+fqrleF 2YeB810tgu9qNWkQRcsczQ==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20170824000000 20170810000000 37284 . WjEAJUBZwFd9q4mq2n4hTf8itP5EjcZe 8LmggAJ/eUlHzram+owSwIo5IoYeEdNU y9b0yyXwQWa0DZH7zALKiSi9gW/Bglft Ejfdw8u+XXHgCcI17naRdEawVIWaQCuk Hnn4Rp8aapfQRSqcxCPa42W5BDy8cEXe rhi3yGr6tyAsrya+8ZrIM9U5c1+1fXvR G+wWQ8q4TP1SsuG3G55pSbY+N8VMa96c IhM9PlmZ8H1OtMokvKPYL6tZi3DQfN+f PJbT7ej7OcfogbhoYkRAWibgMM7R/dee M9zIEhBJlaU4KmuFthu++Fe2Tbj6Sp5r ooTLb65UnkoWbGOYhgSamg==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170824000000 20170810000000 37284 . ypCP7WbleHUNZI9xB+3weFFWqM2Ery6f +bic9OtSyFjAkTmAb4z2B737fAhEALNe CFr5NgEvG+9aRrETUac+vs7ljA6jj/gQ PgVE8cE4bXXl3sWnwzgSrG3HUyxYFNJm n16Joj9RA84ldgsxsZ5mDML1uH2GtyZe AvIqsCdtIKI/WD6QnqUQyCPibYyXbGLf NNh0K8wosKKrA/hx2y4oIHEXEwf0yA0f PzQQtn6/bHEzWYpvzkl3HT7gYxSBmH0h v0av1Q4MUjhnUBNwayYAmeXd0xfKV7DN yGqr2pzLuziC0RkLBiC85qt/14kN90ZH +aKeSeeRxx6YaGU1T1IVcg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20170824000000 20170810000000 37284 . E3PCc79NFLHFfnf2+WYF1PvcIPYEF7cf CghVd0wWcP5B1Bh+vEdo6gsJaYD9/QtM q68IW4bPm49dI0zRvkRGG4NjE8zU8bO2 wGf3Ky61F4Xm1fwq9TYYCT29ofkm0NpE 12fP5P/ahA6K7cw7BfDPjLPhlqw+/iwO IlEKWbeg6CT33eMBfhQC1B/47bLon/AU 4AAd+sy50sMhkTeId7Zy5BAHg6V09Qg/ FV8PlgiSUgmSiEa22gITJOEu2LtRYPFe cJHEGZAtKAt30sQ0o5dR6m48mWdOo/4B GFoC1HfrpsCyGtpi9Ab3jFjwtpGFqu7X wzQS8OlqBPFYA33OJq4f+A==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20170824000000 20170810000000 37284 . ut9CvNq5yKmhwZXGInuB6eVW8FPmKixM Lr9Vi4wbLo42Qr+nGNlhAKuDM160tZ9V 2D38DskRYwucSZ06UWb5zuuQ+LEMr7np Q2J8Ydt/ecXLsIvLYs45S5W8ovJmTnss qkU0fZIb9ySwdWW2rYqE9Hy1PadKS53m zFyqIA7OtsHcear83X1P4Jwzp/s1PA+N fiNePt+UoyRJXCgLJjNAI4Ay5T0HMD4n 00S6aQS7VmKWsJmQ7Uzyd/Q79pLo5mIQ MAQjowlcYSMcOen4JFKeQyiEEDfq0/Kp yvEP//mVgnzANm1QqALHwJuuHKRvg4Sw 3En8feCAesJLjx86GMelxg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20170824000000 20170810000000 37284 . WjEAJUBZwFd9q4mq2n4hTf8itP5EjcZe 8LmggAJ/eUlHzram+owSwIo5IoYeEdNU y9b0yyXwQWa0DZH7zALKiSi9gW/Bglft Ejfdw8u+XXHgCcI17naRdEawVIWaQCuk Hnn4Rp8aapfQRSqcxCPa42W5BDy8cEXe rhi3yGr6tyAsrya+8ZrIM9U5c1+1fXvR G+wWQ8q4TP1SsuG3G55pSbY+N8VMa96c IhM9PlmZ8H1OtMokvKPYL6tZi3DQfN+f PJbT7ej7OcfogbhoYkRAWibgMM7R/dee M9zIEhBJlaU4KmuFthu++Fe2Tbj6Sp5r ooTLb65UnkoWbGOYhgSamg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170824000000 20170810000000 37284 . ypCP7WbleHUNZI9xB+3weFFWqM2Ery6f +bic9OtSyFjAkTmAb4z2B737fAhEALNe CFr5NgEvG+9aRrETUac+vs7ljA6jj/gQ PgVE8cE4bXXl3sWnwzgSrG3HUyxYFNJm n16Joj9RA84ldgsxsZ5mDML1uH2GtyZe AvIqsCdtIKI/WD6QnqUQyCPibYyXbGLf NNh0K8wosKKrA/hx2y4oIHEXEwf0yA0f PzQQtn6/bHEzWYpvzkl3HT7gYxSBmH0h v0av1Q4MUjhnUBNwayYAmeXd0xfKV7DN yGqr2pzLuziC0RkLBiC85qt/14kN90ZH +aKeSeeRxx6YaGU1T1IVcg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20170824000000 20170810000000 37284 . KdU8rLyDvoUejIT/W0O/jldfOdQQgEbj lNkK5OzdqgGCa5ohaOB+7EDk4+ycrClP NzOYc9+W7wR7il0eCzAqabxzm6NBHCJ1 Q7Oci1TQXnx7q3Y2IXKycCk+95qAMEMB h9OmmMLJNc5wGGgxh+4ARR//bIw2sv/L gABeXIQ2T1Iug06r/lBBP+4sTdMOAgTE Y+uLPF4K8iK4JIKZWTPmPlEgrzPiqEFf znjSCXavIyWgsHjFNqrAnGHcdVbz7GNP vVwBfcjjhwBWm1LIY68yQtIBWlUyXUgS 3ieegfiz4GET13SXEM6SZIDPrstJ2qhx Zeu4NlBA0zgspOtCwJzO4Q==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20170820000000 20170829999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20170903000000 20170820000000 37284 . VwR/AJaqwjI1aDnB0x0KyuUZW/AA0RkD XRHFiyLWs3z5rYSMHVietUAoS1gEqd1+ UHhfwzhdKNwtINfqBQEvALqD/Dl0p0Zb XrwMvf2gk8Cdotgh6vbDwafcySr5/xyv ryy4S+6tVm4APLejzBN7Qsnpi9FusNvE 9Y+d9yKHnHTLTpAihKEbokcsACu4Jwo1 QAJk5LxlM6a72iUlg/Ce/hYUxZrbyt+n QtZhP5sK4cX5v7GeblV9BWsA2JGRQ/IX oGY6T9BqjNi7CA4oxIaurbdrOuAXv9Bq t6vmue/PaIN+eEaqCvHMn2wj5+bgJ5T+ dMSMxgpbg/kIE5n3qYv/nQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAe1udmhwaWyg6PD00YReHeT6nYeN hKAy6oKMc7X5NXXJq6baVZT5p3AnFm7B 0ReT1dOpOjZuArydSdASupCQwRPVja+m 8PQSaAdThTifIBbP0Y2TeeA3yfGbHubo scMezJAXr7FGDQ8XIfyUYBDBoDaINi6j V/oI+aSnwm0uWdxHqfonzioTYenuTsax OGLYYuCMn9soxIuXatO53MpqfsJgh7UK FDCDGKA9pFts1vTQdCMPGgEsuYHjsYF0 /kUoRAQJM3jh0t3wE3JQ/HjFIyR37NCM w+i20b1v6/Xo86gy2nhXq2BopawLPEh0 Fv2UCCI7f8k2JvvF2o9udq/N+FE=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcgeCFZiwMD0S6TVgSK3Ob/MKFo+ dHYeNgoOjxH3JoxV09WShBXID/LwKs/e sYAIzTOBB8Fu9IDoVuQe5prOEcGZRp6u zF7JLnqrgOzoaSPRmKsvXh8DfipA56qE AqNwYBCqzlbGIGjHOQ8t+Xonp4fSBYbN MLmh7o8+u55k7PxemKTCiAnY7Bjx0g3m J1HuFvQXDDdvyletuoS3NKqrkjqdOHRB 3p3GKrKgw+zVb05UEyotCEKjqqi+BOmg cm8rmUIeaNQREjxrUyr8/Ry67h5NJLfq LAqAnSePf1PyhQcl8XjGHXN7w1tLLyJu Gy5B3zLSW1P6jIjon77PSn+cXi0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAfX4eSO5BEPXggvx4jL5HkEjbA+5 QD8Acnh4wOHNv/OJX4QhKnpoMDOrpp38 n0Rgcr8qZ0XUqiJWl8eEH//wrQDHoO5/ JHRmibVrcA6UqfhUa25D1BfeMtTgMCSS +W3/heB/YhyXOJtOdWDmJutDY1nfJUSK rF6XwBj4us7U5jvwXwq+l36AeI4q7I87 Std0GNgRdA6/cJtApPoGv/0oNE0iNTyu IBcAR+AeWu32+JavhTycmtMfZ+KOIShq uI9RHzChZ9yLikAT+gclJGLLlxIr6HDr SLChCG90/yvmc57dLn8qhRWRJtJeLT2V HSxeP7oREb6RSdkxET6OTf9GYHE=
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20170910000000 20170820000000 1867 . BX86vECnGwrEZUK/uEaActkHGytrF6G1 2bgGFiWWBjpf3pqaPodMP3P4n2vvYejg bSoHf0SIlV9no1iYMZWZon3uTZcgjbuD UYppPBhcT2O3dlv0EF8Wwmp7NEEp0HN+ 6GRyK50kGlRbQpkmrLSIlMkPiev0kAYd wzt9OzIxMfGrAPiOLgF561rIYZC0mJi2 MkvDJyr+D0cUE7jOWFchMqUxHjal+jY0 aWcM53Kf7XBi/58dzct/rp8m7bedgKtF 8mCNCeKPMpsjtRbFsiehjl8oF/EKvLCI PSie9a4D7N/eRAuIqIpcjWz2ncPlyKT4 2ueX6/NjJaBVEFSuORWdNQ==
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20170910000000 20170820000000 37284 . Z+qzNq341hMzghshwHURDxy96C0zSup+ f5Wa2p0OVlvfmQvxhGPLi0cMAVUib8IP PM9oAfK7C5/Gj0hZnVjoEnc2psZu4GdN GKc0c3SybJoY1l3gDM1llRMctdorllVx vE2WZJh2PSlA+9kW7s3QWMCsAjddyQUf dsz4C9cNY9hb+dSQAcLeodwvbWSANvBV wiP0+qSdNuT0Fbqbz/dvJOHVBVUDLaHD tVcI+q3Lw0GzzvAZuV9eMGizCW6XJ6Ed JSsfTAkuZ325wiLz2GDTAB1QP8i5IEFN 0GqmuNj8n7U1loqNNd+Dj+bsSb9g7k6e JjPE9N7UP17eu/R1zXfu+w==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20170903000000 20170820000000 37284 . UowTPtGOKWyFFHehAXDf8r89LasrvoSx MXuWBpk6/zvXBLJQiNTKLpkVEe6yPqtj B4sdI3BvvP1Vi1Kk/oXUgmsQVC+fhlFI 0lLXDoXcjzCI3IEju+l9YyzAqSfhtzIY i0an070u8WUtyN6onlislSlM1uGi3mrQ rq2Rca5atwEop1cAo1Merl0Ll9yI4vVp FR4YbMaj0JI/cixrFZ5n5srDhq9s9h4F n7h4thWvyA5SujXPW1bPHEtOW7hx5hl3 Zi3xmRQAuzzsKsj5ID+t6JVtTcm6u064 kSrvWOwWHYsEHCmuZ/p4TyNnDtjFnVE5 DOedv3vxR8GUUCf1IfQ4+Q==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20170903000000 20170820000000 37284 . WiRICxAPQ1iK7B5hPT8TEK6PMhZauoFj qtw8sR922TGye2CPMgBWGAmqF1wjnJAH SWYgz1r6vx+QcI/HGwVxbnfjVUq8JmZx EFxbChlcDOxqv2YdLud0wkDlrP/nTwjF CNcY+OkqMDtUg/3LUcbJfGFO2b5vhCDS w+ETff6ZZS4wg7Joy86xojcRb6hWhDFm ei075+N06io2T+RX9YH7LNPXfPpC3OaH cn/4neNoxDe05YkqubB4pjLQanrIF64z 9fqPxDp2T+Ln0TDezMRR+dKBOK+c1VIm mfRMEeEHk6tmH72outgEDVUXxV9stjN3 /YfLs7kv3KRaqYLe8D3hvw==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170903000000 20170820000000 37284 . TQthucelRM5g+wAyl7sbwXfHZLZsozys ppIkTZV4mrbprZfqPxP8Jks7Be/Zcvy5 nftAOwD+RXOA6K16qTR6V76zpuBnfUEs emufRjCmbb41GOTBFcn00zp13u0q+x7T VEBMeM/1AwJR85cArTU1f0GZhLCYxino 9U6n9RlKy0b60tduK42z6B7px9wok35U 5MmkXop6NVxM0LR97ox6EAPOzUZZkUgC D20mx9gwhu7szDIMY2jgjWg7V+QDOaoB /JYncYAdy4O6sPGtL88vj6l3A25mvAe3 86c2SZmmiYBc20Fg6sq9qbH9XH1GFK/M iJrNX4FT6+fn2XAIcWOj+g==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20170903000000 20170820000000 37284 . VwR/AJaqwjI1aDnB0x0KyuUZW/AA0RkD XRHFiyLWs3z5rYSMHVietUAoS1gEqd1+ UHhfwzhdKNwtINfqBQEvALqD/Dl0p0Zb XrwMvf2gk8Cdotgh6vbDwafcySr5/xyv ryy4S+6tVm4APLejzBN7Qsnpi9FusNvE 9Y+d9yKHnHTLTpAihKEbokcsACu4Jwo1 QAJk5LxlM6a72iUlg/Ce/hYUxZrbyt+n QtZhP5sK4cX5v7GeblV9BWsA2JGRQ/IX oGY6T9BqjNi7CA4oxIaurbdrOuAXv9Bq t6vmue/PaIN+eEaqCvHMn2wj5+bgJ5T+ dMSMxgpbg/kIE5n3qYv/nQ==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20170903000000 20170820000000 37284 . tQAEQyOmVLpxqHu2JCh18vsX6dy5B8a2 KfuwvoIBtTTSqZkJb7X0nHukqnKMnKR9 /IIfgyH8gi5gMJcaDAxqNzwiZWndstJF z/d7VfXUdIKDu5mIxDlylVNO6OOYRe9R RkZ256e4i4IoWsyYS8temy58+j4/+ohV PTIZe0rPmhgH7YEBioWnUd4HKbeQnMnO qwICbPWzlPiNTNH74DOYQS8hF41FDkDl /c7zjjwWaNHYLGFOSQR7aeg0uQajZk2V 3jVmv0wTLlivF3Z3T+Yl4OLuW4jm0e12 rsYX6kwdEZ49PQWOaMd5eyR5MWBbFja5 tHuZ6czAV5NzBD4NY17z2Q==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20170903000000 20170820000000 37284 . WiRICxAPQ1iK7B5hPT8TEK6PMhZauoFj qtw8sR922TGye2CPMgBWGAmqF1wjnJAH SWYgz1r6vx+QcI/HGwVxbnfjVUq8JmZx EFxbChlcDOxqv2YdLud0wkDlrP/nTwjF CNcY+OkqMDtUg/3LUcbJfGFO2b5vhCDS w+ETff6ZZS4wg7Joy86xojcRb6hWhDFm ei075+N06io2T+RX9YH7LNPXfPpC3OaH cn/4neNoxDe05YkqubB4pjLQanrIF64z 9fqPxDp2T+Ln0TDezMRR+dKBOK+c1VIm mfRMEeEHk6tmH72outgEDVUXxV9stjN3 /YfLs7kv3KRaqYLe8D3hvw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170903000000 20170820000000 37284 . TQthucelRM5g+wAyl7sbwXfHZLZsozys ppIkTZV4mrbprZfqPxP8Jks7Be/Zcvy5 nftAOwD+RXOA6K16qTR6V76zpuBnfUEs emufRjCmbb41GOTBFcn00zp13u0q+x7T VEBMeM/1AwJR85cArTU1f0GZhLCYxino 9U6n9RlKy0b60tduK42z6B7px9wok35U 5MmkXop6NVxM0LR97ox6EAPOzUZZkUgC D20mx9gwhu7szDIMY2jgjWg7V+QDOaoB /JYncYAdy4O6sPGtL88vj6l3A25mvAe3 86c2SZmmiYBc20Fg6sq9qbH9XH1GFK/M iJrNX4FT6+fn2XAIcWOj+g==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20170903000000 20170820000000 37284 . IdGsiFPBibUlbjGQcsm0y6HzVk1bpKhe odSvmA3r8si+1AkAzyGbLC7Cd1gmQJfO pMLQ7AHZFcQptp07oexFqqCI9HPVoj1M ItMZK9eFUPiZKM1pXnUuuxremUQuOPnJ NMfECR2qicVkd+K+383gd0I+qyfeyVkl VbdZzGe+2sPRkj+9h7rvWAPU77JMb6To dS0TJhWb/kH7k7+DXXcT0315IqnjKn71 2xoNN8rb2afYdgV0kMXUvQCd9JqtsIBT RnjrB8Vru32jc4TyKCrm6YT3kZb2MzWn hMLEkZfi+OBiOeVpULqkliLUH1os1pH7 u/Kdsizxl1GdpzuVv2TC0g==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20170830000000 20170908999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20170913000000 20170830000000 37284 . hGcp4EeAF4ETIwVvhdM47CKT7ePfuiI1 l/bEWXwsYfd0XHUgORkdfPouInN40vIj VXtO/rE8HU6FvfisBTcC23GCH3sMFDYT HEcEDXAR9FczdXtDIPXYnRL/TUCIo/f8 /3iNis/QUO4LRedG+dgSclYhvOQqIcbZ I0crF/JugXrrckXhkM3JQhjbK3DSMy5/ srY63HkAKV42kVLJPTcTeZ6Jse4wej1U t46mqkB+YfjM9Cnkc/UjGFgaaq5+XPIz UIZ7oLBbdtfTR4hMCGpAjLtpxqICdvbE 6pKGxqPBGtBYDkupWzZV08X4Lmr1bdwf CCgAIqCwy0xiBbk/zG/Ufw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAe1udmhwaWyg6PD00YReHeT6nYeN hKAy6oKMc7X5NXXJq6baVZT5p3AnFm7B 0ReT1dOpOjZuArydSdASupCQwRPVja+m 8PQSaAdThTifIBbP0Y2TeeA3yfGbHubo scMezJAXr7FGDQ8XIfyUYBDBoDaINi6j V/oI+aSnwm0uWdxHqfonzioTYenuTsax OGLYYuCMn9soxIuXatO53MpqfsJgh7UK FDCDGKA9pFts1vTQdCMPGgEsuYHjsYF0 /kUoRAQJM3jh0t3wE3JQ/HjFIyR37NCM w+i20b1v6/Xo86gy2nhXq2BopawLPEh0 Fv2UCCI7f8k2JvvF2o9udq/N+FE=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcgeCFZiwMD0S6TVgSK3Ob/MKFo+ dHYeNgoOjxH3JoxV09WShBXID/LwKs/e sYAIzTOBB8Fu9IDoVuQe5prOEcGZRp6u zF7JLnqrgOzoaSPRmKsvXh8DfipA56qE AqNwYBCqzlbGIGjHOQ8t+Xonp4fSBYbN MLmh7o8+u55k7PxemKTCiAnY7Bjx0g3m J1HuFvQXDDdvyletuoS3NKqrkjqdOHRB 3p3GKrKgw+zVb05UEyotCEKjqqi+BOmg cm8rmUIeaNQREjxrUyr8/Ry67h5NJLfq LAqAnSePf1PyhQcl8XjGHXN7w1tLLyJu Gy5B3zLSW1P6jIjon77PSn+cXi0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAfX4eSO5BEPXggvx4jL5HkEjbA+5 QD8Acnh4wOHNv/OJX4QhKnpoMDOrpp38 n0Rgcr8qZ0XUqiJWl8eEH//wrQDHoO5/ JHRmibVrcA6UqfhUa25D1BfeMtTgMCSS +W3/heB/YhyXOJtOdWDmJutDY1nfJUSK rF6XwBj4us7U5jvwXwq+l36AeI4q7I87 Std0GNgRdA6/cJtApPoGv/0oNE0iNTyu IBcAR+AeWu32+JavhTycmtMfZ+KOIShq uI9RHzChZ9yLikAT+gclJGLLlxIr6HDr SLChCG90/yvmc57dLn8qhRWRJtJeLT2V HSxeP7oREb6RSdkxET6OTf9GYHE=
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20170920000000 20170830000000 1867 . o7N+8dSYDD37wWTtz8ISO3Z2C6Jrill+ r94YmxQQWimsbJ3LEihrjGyXL7Lr+Kgw rpbYUgjwzx5JvDOIUSqmM2SBxsg76Rhj 5NAARoNjR4GQDQJ8U6txxXVO1X6Llgly mBTi0ImUu0p5YZz3HxWpPJEoYE1goiIl xE7G6AFN+nd+riO1tbaMG0X1+9xw5NYt ImuErToPcc0ylMzpvXBLZqUg/s7bJHyL HdoGghslRLz2W1DXWiAnBvICBB/HHPpJ 5LSncL5h/d2HSwVUT30NkS08Vse1aBBX YZpAoupxeYsZ6KPPsdY8Y3xary1mASdw 0RYC6AzGMjuhtevYEPmRsg==
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20170920000000 20170830000000 37284 . UkQ4HtPbhAKw5BP64OqiZO+xKisJzIA5 YfTAnriF3MiUSMlcx9BYAkrUNkJYWzlZ YgWtAsuE7k/wCYGDxg8CHGTLTx4gCErh gKxAZu5HIz3tyUYZghPT+OIDV0X+hYVZ ZgAO+8w8oSDab3sS1j0ye1weDldhjt9o qEbVhy7gZo0MZpTz7n96TbLgjSF0uT+h Xpp00H/10hgDbvjimI64D5KuHUusL964 h9XIcoZ0vQW6aO3Ufve4/9ma7n+MDGna Ci4/uUGcYA6aNqi+hF518Yi/A2v/tBnq QK2mbnF3POV+kpmQH4iQJ07X4aaeu0cX PsgIYi991e0SFZ6jBWX9Lg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20170913000000 20170830000000 37284 . oHsPF5SHum+uJ0JboRPVvwPXPWlcrfW3 sp0MYYsdVVy/H4EetBzVl8I7xEmSI6c2 LY4rlxYtWKxUuDr7mcvlwbPIRG7Rs+C2 f737cCNlm8mBVNEpCcxExnf4swVssIwM bFfLfr8hZjQ5YhU+3Y0L2/YiCBwugRBb vLpbrmuI4th1a+wb0fDF2WYS5GdUzinI pRBF3ZQdoZIMXFdbCvDVGxwd7yBkXxq3 l7u7UK1QBWZdutS+RhHvG3Am3lnKaF1+ tEzseXWDPh/KD4BX1oDrxROdATif3FtJ ktD/1nb/DgIxy12fLx3+NMtCRV6Q+iOa FyFZn6PkltoMEa8CdY1tiA==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20170913000000 20170830000000 37284 . iBK2d08iy7leYXYJKClLj5glo+fxB+zT bRuqnF0qIHEhEDHqo+i+9wI0c5hq6mWL PXmPUrjZkdkmeQF8xreI3GcXATbV49yu v7d7RkmnoiyMqRSEZODx0tH0JugZROUv to4KBweniM9JNCo/iwXJqUcrtawdY4Uz hYn/fvpsHw7DhdEh4o634WSFEgsPvW2H +DcL+xJ4GMfdF2RV35kd/hI40ILMqbbU cFRzcXX1r3ZK+2JOJ/InkjYmusnShB/8 oL3BIXW9g6d7EXA1qjDlc3TD1zOC0tPW G0cTOWpRZydvnLq5yEFVpbNbquXOfB6D 3u+kHoEQ1T5a1EYkOdQSQw==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170913000000 20170830000000 37284 . eMd6DeVqe/USIOeZ5MBNfxArFmnmEpnk +Kl9yrnLBISN2PkkcrOY/D/zOQ7jMfbd MB0HUt6R3a7uQNMYhsuLnIQoTPeZdflu I6QNPFCntf53KZKtvr7AcnTex0yx4IJI n/2+xbrdROLbe5deFccKsGvIXA6MkTQd zP8vmOg/1ltpN66bMSNouaCbDC6aDoMs ABBneHYaGnYlxA4aTByRtJzUh+IGEMvB 3eNwbuQ2vbkJhkKrSsJubIqhjSEhNEdK oYmgzUsFL0M3vUsTFbhTASr/ngISJN69 fMJ3yrDPdjlJBXkkSlPZUWnfKHfxjDbR /6Ul50cpb6wcP3ll++u7Qg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20170913000000 20170830000000 37284 . hGcp4EeAF4ETIwVvhdM47CKT7ePfuiI1 l/bEWXwsYfd0XHUgORkdfPouInN40vIj VXtO/rE8HU6FvfisBTcC23GCH3sMFDYT HEcEDXAR9FczdXtDIPXYnRL/TUCIo/f8 /3iNis/QUO4LRedG+dgSclYhvOQqIcbZ I0crF/JugXrrckXhkM3JQhjbK3DSMy5/ srY63HkAKV42kVLJPTcTeZ6Jse4wej1U t46mqkB+YfjM9Cnkc/UjGFgaaq5+XPIz UIZ7oLBbdtfTR4hMCGpAjLtpxqICdvbE 6pKGxqPBGtBYDkupWzZV08X4Lmr1bdwf CCgAIqCwy0xiBbk/zG/Ufw==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20170913000000 20170830000000 37284 . bUh3BmbXKHB8ahKfaXzpOSdof0MAR/Qe U97k8jR5CDK3LFWF9JaCiSA9QHVSl8Mb dK/yPQmZCG5//jhW7rddOGo7TSmG+fqC oZtwJS9/HsySuvpBmxiHVjwvmJsbtCqu Ob8hxJO1S0LkAKEN0n5zUrY9wjbzXvSd Vr8sVzpJY2r+T8MD1lewERQtb97bzvGV LYK6lnNJoH6KZwAJd6krcVJWVt4ENj51 6sOiGslAG8FdTuLU4DJSr85y1hag8sJl l3gJWpPfrEJN1cgrWWs91VI365rIW5ZQ zb/vAauB3s6dQ2BrWOWJnepSA9tF4GES siWsiE1feIUsWVaOYCmCSg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20170913000000 20170830000000 37284 . iBK2d08iy7leYXYJKClLj5glo+fxB+zT bRuqnF0qIHEhEDHqo+i+9wI0c5hq6mWL PXmPUrjZkdkmeQF8xreI3GcXATbV49yu v7d7RkmnoiyMqRSEZODx0tH0JugZROUv to4KBweniM9JNCo/iwXJqUcrtawdY4Uz hYn/fvpsHw7DhdEh4o634WSFEgsPvW2H +DcL+xJ4GMfdF2RV35kd/hI40ILMqbbU cFRzcXX1r3ZK+2JOJ/InkjYmusnShB/8 oL3BIXW9g6d7EXA1qjDlc3TD1zOC0tPW G0cTOWpRZydvnLq5yEFVpbNbquXOfB6D 3u+kHoEQ1T5a1EYkOdQSQw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170913000000 20170830000000 37284 . eMd6DeVqe/USIOeZ5MBNfxArFmnmEpnk +Kl9yrnLBISN2PkkcrOY/D/zOQ7jMfbd MB0HUt6R3a7uQNMYhsuLnIQoTPeZdflu I6QNPFCntf53KZKtvr7AcnTex0yx4IJI n/2+xbrdROLbe5deFccKsGvIXA6MkTQd zP8vmOg/1ltpN66bMSNouaCbDC6aDoMs ABBneHYaGnYlxA4aTByRtJzUh+IGEMvB 3eNwbuQ2vbkJhkKrSsJubIqhjSEhNEdK oYmgzUsFL0M3vUsTFbhTASr/ngISJN69 fMJ3yrDPdjlJBXkkSlPZUWnfKHfxjDbR /6Ul50cpb6wcP3ll++u7Qg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20170913000000 20170830000000 37284 . Z7ZBkT7afpWPO2bxz/mItkegNlUuRZIe BcBx+AYTAvs8ffIhm0EYS62j2AVIZwwj S/WnenaOgEvQDtoKi4c2vD8SvOM3n6Ia g6GagJrDTFhVcd0moZdzPNiIukfSg2RS xvO7SckRlp7ILApY6P6EfvgT5tjA39O6 VlgtKcwa8h49PsFdPwpqH2FtkG0i0iUr v4gR/JqzG/ZB+07KF7AtbxLfvvj3o7/t Imal3xl0OAscxAN8hKYRQhRKs4snryXY y9ar5Wf2JVHoyRPJ/rE00UK7/zYr9qIx 4QcTKKdes0prgtu7xyiouXzOSkXE+4KU 6E1tHxRip8Skk1r1XVZwwg==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20170909000000 20170918999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20170923000000 20170909000000 37284 . 4sVe391XlvS128uzSEJZuV+LxGPJutXl UpZs3zvvI5LYJH0ivfIMq/ju8wyuVMXx tppwUz2chDoVF4VG9U09R/OXX35EFtn+ 1b77QwoGdciYZ+BTB7mdvY8Ft1M9GOmn 7G+bUOmdQ5DBY6DxgrqnCtnPHtXbsbnU 07PahqotEPXpPntxN9QG/F9i7bLm6O0j qRGFeAHA37aEjkjCKLkAQ6mV3u9zoLY7 UAS+/eE2QwzvitGjaRprbnS3djb6vG2B s0c6/udgypOCY/Ocs7nTRAstBLnRhTXO dmLxaQIviCNs84/gD9ZHa66n5lyqpM3p xwkly7gophmpaad2Z6sG4g==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAe1udmhwaWyg6PD00YReHeT6nYeN hKAy6oKMc7X5NXXJq6baVZT5p3AnFm7B 0ReT1dOpOjZuArydSdASupCQwRPVja+m 8PQSaAdThTifIBbP0Y2TeeA3yfGbHubo scMezJAXr7FGDQ8XIfyUYBDBoDaINi6j V/oI+aSnwm0uWdxHqfonzioTYenuTsax OGLYYuCMn9soxIuXatO53MpqfsJgh7UK FDCDGKA9pFts1vTQdCMPGgEsuYHjsYF0 /kUoRAQJM3jh0t3wE3JQ/HjFIyR37NCM w+i20b1v6/Xo86gy2nhXq2BopawLPEh0 Fv2UCCI7f8k2JvvF2o9udq/N+FE=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcgeCFZiwMD0S6TVgSK3Ob/MKFo+ dHYeNgoOjxH3JoxV09WShBXID/LwKs/e sYAIzTOBB8Fu9IDoVuQe5prOEcGZRp6u zF7JLnqrgOzoaSPRmKsvXh8DfipA56qE AqNwYBCqzlbGIGjHOQ8t+Xonp4fSBYbN MLmh7o8+u55k7PxemKTCiAnY7Bjx0g3m J1HuFvQXDDdvyletuoS3NKqrkjqdOHRB 3p3GKrKgw+zVb05UEyotCEKjqqi+BOmg cm8rmUIeaNQREjxrUyr8/Ry67h5NJLfq LAqAnSePf1PyhQcl8XjGHXN7w1tLLyJu Gy5B3zLSW1P6jIjon77PSn+cXi0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAfX4eSO5BEPXggvx4jL5HkEjbA+5 QD8Acnh4wOHNv/OJX4QhKnpoMDOrpp38 n0Rgcr8qZ0XUqiJWl8eEH//wrQDHoO5/ JHRmibVrcA6UqfhUa25D1BfeMtTgMCSS +W3/heB/YhyXOJtOdWDmJutDY1nfJUSK rF6XwBj4us7U5jvwXwq+l36AeI4q7I87 Std0GNgRdA6/cJtApPoGv/0oNE0iNTyu IBcAR+AeWu32+JavhTycmtMfZ+KOIShq uI9RHzChZ9yLikAT+gclJGLLlxIr6HDr SLChCG90/yvmc57dLn8qhRWRJtJeLT2V HSxeP7oREb6RSdkxET6OTf9GYHE=
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20170930000000 20170909000000 1867 . nYKCrIGWK/cioGfdAArMBL1Xw4JTRSnl 9hWCY26Ib7ogWnkJd5zp0WIbjNNJiNO/ XRDUZEXC7anjXzSt8ZrncqMsFI+fQD++ ZJpmEZpz4FkNrGN0yooO5uPJNxznf8Rz /Zz4i+DUWCJWyiUSOVZ/zFTgv7oTOuat bqYuXNp0mLk1ue093M5pOYTLuzuGDWlZ s08ZhO6VPV+9nfEjnqKgRyNKSiQz4juS GMBQE0nLF9oWv8Wl4h8XKuXPPYAgg4Hf J54ZGckuDXLG1UU3/d7iLmbKWRsPFyRn e3Jm6bWQ51C9BJhTjRmQYKdEPPTUhVnE wNsn9VekiSAeRNt6Fv/e8A==
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20170930000000 20170909000000 37284 . jQ4JxIi9y6o9RG+pqB9OXt6XWKNU082y v/tDJH4O6XwaeAcM9lq75K62u0jVc7iF ZOxOBglei/rMC0JwnBDmU27gn1vWmpwy cUtlqK5b7n+EgydfLpAru9bCgUD8Cn8M qQ3gpsClpZY2l1fRAsk3BQa/OpjBCjmX +LvikL1t+bzgH0198BQrNbgNjBi7kr28 8YxH4K+MqcLA2t+uoHNoGxygectBMURG 7luS0Cy+Y1dN4YUERrX0F31toLfo+Qpn KVtxrA2Aw0iHhWChBsVn1IHGYvnmbN7A u6IbaggzrNru6t3OqG6sxLZd6gKSuY1d SfOU0RwM4f6dhZKz0Cyiwg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20170923000000 20170909000000 37284 . nUcSfFQOC46LyoBiQSVWoUUop3AU/sQa FjqJKG3aL9sTk2Rm+VzbKbpzlFyBCxcg nChFWq7m5EjzmeG379dTCM/6Imge+npQ jhsgz7yHh4jwfYqgOaGFp84fduBFlbLc XvJvZvjmnxsPlB6fYOlBKuXEANzLJgob tlle84oGusjWpbPW/eNtvMAhPG9Ct0CF zoL2UkqO3wp2V/gxPiEPmNs3lr7ov95J kltL2AbL4SmxQ9T94FLqrJWmUCKAHjdF bF27F4zfwCVu1f3Qrwa/77H6o9KAtvZm tevGo/lsO2xEWK0QV2Olex6KHQx1NWKy cR/7GH11NBnsjYmiglLIUA==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20170923000000 20170909000000 37284 . g8bM4iGPuB1ZyAjP39aGVi21hUhdrsxu gqHBb3xRne4L79U8rHc1SHpA5BupowTl LQJV+3pM5KnX/eBVenWf/4y6gjI7aj/l u1chVmUfRKeI4kTXd+Bhhv/9SJXEvHqw 10fcWavusnuHs/qNCZlZCs0RmZaaEuhl EJF6Vat2x56/dEGw08KToo05OR4esdq1 r7ARQlARYZocINyQgB5JqgxV3CJ87LEz IwGu3pOJGwBA/Lqg6LOqv/TCREwWJITs 2cfFpwRyKmiGd7Qo0k4+0Dw9nIupR4rj KM1EXklx5YwAG1dNTQUu6lmQ7BJbSXs6 x7uOjdl2id2RQfysiTNokw==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170923000000 20170909000000 37284 . 6u3jOiK1+7BoFdnFwHWHDGRYfmTAeQV7 X2yCqBtBiaAUbeTrH14ycJdyd++jL/fa w3nZa+xkdxACrsMu+0rWzH0zNtP2gbg0 iI3FyxExBWnlJfzK7fRAfzE6i7OCTjJo 4dTa0VxV/lJN4v2criQ3QdmJgw+Yzpmu Aac35W53zHioliK1gEhqZRAyNLXVvfXq zJYxgAzmIdGwoS6QyJXx1Sxsd7HGrosw HdhQPPY9rI2ZXzZjat68bpDXw/jqTdi5 vw+lD9STlXg1ZrWWAN7Nip19JEteUnC6 EyoUPggFdu4hG6GUIIJKjbM0SKcUGBjn F8HZJH8LIOap2UoVUkVvQA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20170923000000 20170909000000 37284 . 4sVe391XlvS128uzSEJZuV+LxGPJutXl UpZs3zvvI5LYJH0ivfIMq/ju8wyuVMXx tppwUz2chDoVF4VG9U09R/OXX35EFtn+ 1b77QwoGdciYZ+BTB7mdvY8Ft1M9GOmn 7G+bUOmdQ5DBY6DxgrqnCtnPHtXbsbnU 07PahqotEPXpPntxN9QG/F9i7bLm6O0j qRGFeAHA37aEjkjCKLkAQ6mV3u9zoLY7 UAS+/eE2QwzvitGjaRprbnS3djb6vG2B s0c6/udgypOCY/Ocs7nTRAstBLnRhTXO dmLxaQIviCNs84/gD9ZHa66n5lyqpM3p xwkly7gophmpaad2Z6sG4g==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20170923000000 20170909000000 37284 . co+5dOhcVlu7rKvhWgJY2te7PNFQnvt8 DFn2xKP5ZV1qDtId/qepvY4FHEYzTJua DLD4efHBL0uY3R0VONqnHS/DppUBRXHt 1JcjFETuMt2pZszEDyEimFDLNCtclmnP 13A3fwDObFZxxqHmcQXV8pb4ZBInJuCs 5SCU1NE0XmBjAbpgB//Nfllht+G1SQlD 3XHNvlNBQ6D7Ma1qs1w0XIse5ekS09nq QRAJbgu6ob1Yb9b3zuvcFyU357h4MJPb ligrO4HrPY7Q4w6M2GUKgnCUVlPG9Wrk vgnNNMOIzVAwWY95vr5K/bANTjI6Mh6b Cr61MpLk5Y7JTPIsyMJgrA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20170923000000 20170909000000 37284 . g8bM4iGPuB1ZyAjP39aGVi21hUhdrsxu gqHBb3xRne4L79U8rHc1SHpA5BupowTl LQJV+3pM5KnX/eBVenWf/4y6gjI7aj/l u1chVmUfRKeI4kTXd+Bhhv/9SJXEvHqw 10fcWavusnuHs/qNCZlZCs0RmZaaEuhl EJF6Vat2x56/dEGw08KToo05OR4esdq1 r7ARQlARYZocINyQgB5JqgxV3CJ87LEz IwGu3pOJGwBA/Lqg6LOqv/TCREwWJITs 2cfFpwRyKmiGd7Qo0k4+0Dw9nIupR4rj KM1EXklx5YwAG1dNTQUu6lmQ7BJbSXs6 x7uOjdl2id2RQfysiTNokw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170923000000 20170909000000 37284 . 6u3jOiK1+7BoFdnFwHWHDGRYfmTAeQV7 X2yCqBtBiaAUbeTrH14ycJdyd++jL/fa w3nZa+xkdxACrsMu+0rWzH0zNtP2gbg0 iI3FyxExBWnlJfzK7fRAfzE6i7OCTjJo 4dTa0VxV/lJN4v2criQ3QdmJgw+Yzpmu Aac35W53zHioliK1gEhqZRAyNLXVvfXq zJYxgAzmIdGwoS6QyJXx1Sxsd7HGrosw HdhQPPY9rI2ZXzZjat68bpDXw/jqTdi5 vw+lD9STlXg1ZrWWAN7Nip19JEteUnC6 EyoUPggFdu4hG6GUIIJKjbM0SKcUGBjn F8HZJH8LIOap2UoVUkVvQA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20170923000000 20170909000000 37284 . ENRCRlKqQ6DYCQTD41xWMM7vfU+vIsem i5S243UV7pWEb3f67JQyM8ynCPvINQq9 pZE6oruuPlp9HHixMiEdNwyFGPbKTCsr 0o8CTsuRZWKOmVjMtKWOg5F+cmU+uWr6 sWjfQ4ON64Zez8ED5i9p5ZBgTRN4sFAy TC7/xSidQ2+D9tN3EjrUkgECkujGvErP NBFOUukZ18l6IgeboDF9GpJ20xxsnMkT I1NtQ1g5v13M45ct/XYrUeuT913/wdVf HwUjptkF5127XEc4vyRZ02Cy7hv+x5Rf RK0V2IjuTN1MgxvTDsCAbxiNJKesx9lJ DFFD9P6UDQiMr5USjMuAwQ==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20170919000000 20171000999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20171003000000 20170919000000 37284 . zIsYbxp2lc9mFdhHsUmvb12AM8fIcHem 6YgkRu4Owgv/NkRo4yQ2GvfkuSmXb9EU fsHKaJ1CrDQRckXTGtKLvWvFnUuxJtrO DkxKdTUG+yBwL4Sma1xhsj11JFu57Hm7 hQemP9LdYGXxntRh24kNG/ILuj4qk6Oj k+l87JcMnrG8SzOvykliSPriywagnvl/ bEMWzbFRFwEdxr9hkwMNxK4XbAAeEBJ0 j475+LS0FYiijsvqhkU5sjkTPkHzVtTI Ks1POK9cerSOJPm8GtWrduGYvIJjSwmH qVvzKBG5RfUZiAigW/VrqYcPn/8lpYqI kz6lAFl/pGsEd6v2gSo43g==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAbS4LSbmieIk96i3AK8IEZXlLRdM YLoEp4QSRMrdtJ0VcZKsjHWF6g9Q50T/ 1XKBxY/+6j71//cFRi1cViqoNsPd3LEb txWa3Jx/1HBINOkAPdHMBc3SQQ5qEIar PObq98RLnQ22ske1xZtbzNRZogPNi7zM HwQ3GYf0sUVCnYTmv8en5yYfSh8mY9Ez 8okorK7UN2RlHx9B8S5DRtBphO61mSI8 MXRj91aC3iRTbYZndTWorVkj4UvCBBr9 qh7oNpRbprIvAmRzRK6IaxpYDS6iGtSq 05WbGRtQRXkngOzFZO1BBDHgj9Agtx2S HBxAKuX4wF+w8I8xO7m5z0MOIF0=
+. 1814400 IN DNSKEY 256 3 8 AwEAAe1udmhwaWyg6PD00YReHeT6nYeN hKAy6oKMc7X5NXXJq6baVZT5p3AnFm7B 0ReT1dOpOjZuArydSdASupCQwRPVja+m 8PQSaAdThTifIBbP0Y2TeeA3yfGbHubo scMezJAXr7FGDQ8XIfyUYBDBoDaINi6j V/oI+aSnwm0uWdxHqfonzioTYenuTsax OGLYYuCMn9soxIuXatO53MpqfsJgh7UK FDCDGKA9pFts1vTQdCMPGgEsuYHjsYF0 /kUoRAQJM3jh0t3wE3JQ/HjFIyR37NCM w+i20b1v6/Xo86gy2nhXq2BopawLPEh0 Fv2UCCI7f8k2JvvF2o9udq/N+FE=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcgeCFZiwMD0S6TVgSK3Ob/MKFo+ dHYeNgoOjxH3JoxV09WShBXID/LwKs/e sYAIzTOBB8Fu9IDoVuQe5prOEcGZRp6u zF7JLnqrgOzoaSPRmKsvXh8DfipA56qE AqNwYBCqzlbGIGjHOQ8t+Xonp4fSBYbN MLmh7o8+u55k7PxemKTCiAnY7Bjx0g3m J1HuFvQXDDdvyletuoS3NKqrkjqdOHRB 3p3GKrKgw+zVb05UEyotCEKjqqi+BOmg cm8rmUIeaNQREjxrUyr8/Ry67h5NJLfq LAqAnSePf1PyhQcl8XjGHXN7w1tLLyJu Gy5B3zLSW1P6jIjon77PSn+cXi0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAfX4eSO5BEPXggvx4jL5HkEjbA+5 QD8Acnh4wOHNv/OJX4QhKnpoMDOrpp38 n0Rgcr8qZ0XUqiJWl8eEH//wrQDHoO5/ JHRmibVrcA6UqfhUa25D1BfeMtTgMCSS +W3/heB/YhyXOJtOdWDmJutDY1nfJUSK rF6XwBj4us7U5jvwXwq+l36AeI4q7I87 Std0GNgRdA6/cJtApPoGv/0oNE0iNTyu IBcAR+AeWu32+JavhTycmtMfZ+KOIShq uI9RHzChZ9yLikAT+gclJGLLlxIr6HDr SLChCG90/yvmc57dLn8qhRWRJtJeLT2V HSxeP7oREb6RSdkxET6OTf9GYHE=
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20171010000000 20170919000000 1867 . LMUm+N+voM+56KcWjWsPGklkKiyFbgtX Ay/Ji/tJf0a2uh2mdlkXUWAVjPOkafQx 5Tq4zjw+/pWvVwisLiYSn8m+m+hdxcSg viy9etd6dfrsVZNwgsMG3K6LdHfsbuC4 Ydrv6yZoZTUFyfHyQRCfATHJch0piSLL /LMFObaDBC3MWLra6WtdirrYnNTP011S y9tbfC/V7wtDrlIFtkK3zJIsHurm4BGW M9oFfMg7VDBgJef0LbZhhU+4yjTaWRaG VVcSBeqmXqhqlqLxDsqj5mrdIHwW2vF+ Hb9D+F0bwCswKrsKixXOnkCFIrhT1RZQ 0CB9BZH/QGDCDtWUGXueRw==
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20171010000000 20170919000000 37284 . pOG+720+4AOjs6Fif7BUPGOGxGXilWFh z+YeXx6C/IIk08i9DqW4WvGG1o9CMX/+ NN5OyuecUFJp8n22xwfckf3RORs6LhQ2 megC4ihkWAcxXHqyqjqkUx4P7DVAAAGn yzVyXY7KMpuMO7ionGgZk04RxdKeeoT8 8AXKdwNwTUQ61+rNdsUU8Fygg7DLvB3b zT9QkjYTi+QPo6ALwgjHcr6l97hGs5cp U9FCAZXu5oTp9eqjXAmI5eyrDRDRK7nq hSjnbbXTT7GzPvTswda+o906PYkB2gW7 qkGofxD4Ciu8siz3vy9ALUQTT4eeNm7Y WtoCrJbhsZ0/7FacD3L8jw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20171003000000 20170919000000 37284 . uZ3lgEsJfhMjLnVepQJ9SyRoIRJZZhM/ GSl9T1WfxljeWcVuH0OqxOaCGCK27Vpj PWjJ/n6l1zjy3lz1s34/XZ8WmcfbrLdg zZ+9sZsGVXqxT+npo3IeFUXD5a2LgB3d oIidRs/IbkKDaKw7U8FF0SPXS8h1St7V 4/ENnq54WPdh8BZC2f0C9VU+xsnDlp5z NsrqDeMwPZ1yPdnlxJA6koL0F3p1/VYT DP/Mriyzjt+dmvhojkkQnWG2rnnXGWh4 ewlZwlR68mB+DCQ/WhnMHq9RffCTIygC F3Drb6482XR7ANtmXDsNA/Y2iQjADZpb cjb7yyogu5yPHw0SJHfPPA==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20171003000000 20170919000000 37284 . 4OZ8eS+IN3twUhfuk78BkQbVehkxz4x4 EeEoPfL24Vex9AMnIj+DlwgYsErcczgK HgJpoTUZqlGGn2kNzZ03RX3RoAyoBMoW C+1lpVpkv3x6RdNQO0RMtFVRieh1viuQ cd6oFXUsNETjVEJS/spzPEF37xx3lJKl bOLvY65YhfnFtngfYuPTQQ/SEn5mr8jZ r/0iuFs8XAH6I2frpd7h48rD96OeXQV0 Uppw5M3ClEKBSHU+f71M3SWue07HJuBx jKaAZMJYfYhci4vm9NqZe0V/dQWHwRRG OpHwPQNaI0tJBUlHTddKE2pPjxnQXS5t W+o7hHZrH3SI1GsE5Ugsrw==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20171003000000 20170919000000 37284 . oqdH8h+fUxn9TSNLtANXQxUiRHUEraOv GuOZ4hdbEs6K65QY4P4fj/fO7Qi6hlCF O+N2FgG4CTc0lNLQzQ4+FGtpwiczSN5I SCw6lunF53F6ljWl4IheBtvYfdcDgKG6 Oqms9N7cj6eigTejag8qr+mL1/QNe+UI bVGntou/NBdA7B/Co91en6A0jdB8w2CE PRh3FshPPaAvjybZ7vhpaObZf6Q/a27P fFt9o3udlESHx4d8wUhCovfDiXPYBRNd ZOTYfXozedUmwVB9uZ29GR/8cxq8SJhq D69VIL2eqThBK9EbaEyIDhvSDXxbExFi vgvOKzd2/1CLrnCjF3pC+A==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20171003000000 20170919000000 37284 . zIsYbxp2lc9mFdhHsUmvb12AM8fIcHem 6YgkRu4Owgv/NkRo4yQ2GvfkuSmXb9EU fsHKaJ1CrDQRckXTGtKLvWvFnUuxJtrO DkxKdTUG+yBwL4Sma1xhsj11JFu57Hm7 hQemP9LdYGXxntRh24kNG/ILuj4qk6Oj k+l87JcMnrG8SzOvykliSPriywagnvl/ bEMWzbFRFwEdxr9hkwMNxK4XbAAeEBJ0 j475+LS0FYiijsvqhkU5sjkTPkHzVtTI Ks1POK9cerSOJPm8GtWrduGYvIJjSwmH qVvzKBG5RfUZiAigW/VrqYcPn/8lpYqI kz6lAFl/pGsEd6v2gSo43g==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20171003000000 20170919000000 37284 . FYT56I3bIBcEPdHW7pvndYI3Wvw6uElt j8aRh97PR5UsTuwR/ldnYlaPZORD7HXo ia+bKlkHJArJIX49ZrKfCf+/bdLHD6ai AbPzUm0f+jfcPpZ5WO/cXXv97sfOL2Ob mRpQ6jUOmm0fSIU5gnE+V+EIWSgHuWEP w4cy4O6whqI290ktNaDJbLwIoKzuWa4+ WcSAjtikkgkqLejHVWlftT+/5y3d8c9F fWjj3YvCy9J4HPpfCfrgX5dMCeyUTnVb iGwYp50xJJDeaPcXc9HxGfmdJOnzo1se /UGfqjw3J68RHjokzgo/w1Ny56IbgtC7 4rDxGIHjInmeM1+svCbJHQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20171003000000 20170919000000 37284 . 4OZ8eS+IN3twUhfuk78BkQbVehkxz4x4 EeEoPfL24Vex9AMnIj+DlwgYsErcczgK HgJpoTUZqlGGn2kNzZ03RX3RoAyoBMoW C+1lpVpkv3x6RdNQO0RMtFVRieh1viuQ cd6oFXUsNETjVEJS/spzPEF37xx3lJKl bOLvY65YhfnFtngfYuPTQQ/SEn5mr8jZ r/0iuFs8XAH6I2frpd7h48rD96OeXQV0 Uppw5M3ClEKBSHU+f71M3SWue07HJuBx jKaAZMJYfYhci4vm9NqZe0V/dQWHwRRG OpHwPQNaI0tJBUlHTddKE2pPjxnQXS5t W+o7hHZrH3SI1GsE5Ugsrw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20171003000000 20170919000000 37284 . oqdH8h+fUxn9TSNLtANXQxUiRHUEraOv GuOZ4hdbEs6K65QY4P4fj/fO7Qi6hlCF O+N2FgG4CTc0lNLQzQ4+FGtpwiczSN5I SCw6lunF53F6ljWl4IheBtvYfdcDgKG6 Oqms9N7cj6eigTejag8qr+mL1/QNe+UI bVGntou/NBdA7B/Co91en6A0jdB8w2CE PRh3FshPPaAvjybZ7vhpaObZf6Q/a27P fFt9o3udlESHx4d8wUhCovfDiXPYBRNd ZOTYfXozedUmwVB9uZ29GR/8cxq8SJhq D69VIL2eqThBK9EbaEyIDhvSDXxbExFi vgvOKzd2/1CLrnCjF3pC+A==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20171003000000 20170919000000 37284 . SIetFloLSKUteCyhAROcwKu97x30Yjev Xjuo/iCpTSQwPD9Hkw3o6w4Jg4MFI51/ T/TOhr/x3V4AFIon7FyRjYzbe8FBtTCO k2B393CMp4TgcQUDYtdGYwZX37NN7YGr clF5PY5ejLNTT4HauuTObzPB3ZaNLRMd zQK7sTSCWQmjdQYXumL6rYBYLrCD6R52 Tnh8YDL0k2al9bD8FCiQJoqMqHqXYFAE GD0lxBKJrRfubzRmpk7u1zDhfwaP7TfT owkW3V9k8e1UBFkbyBtHaBzRQrnxuCvv BsuEoAx/1q3gItRvj5APM/4Qkmb8oiC3 l0j6fev46O9SQeCREPPXVw==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20171001000000 20171010999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20171015000000 20171001000000 43863 . NqLOBYw5S0U6TN0X70UZv1V45vVbiDSp 1qpD4Sb+dNaCRuz9xxAWyQ4hfAcRh1GB 7L9w5HBviGwqfHQYZQqWvqEjXl5Dcgu7 h0pGQnNfEh2Eq/v16MEVf9XSR4C6ngK8 qtjigNahjsVNYz8COoooQuP5plu1LDzs myTcldGM3EbW9oYEZS5gpPXrMjFn/j7Z 6q87y/1uhJh4hiv5THl5kL416YVQzZdK fVS+oJxJe2B70H7PIOtOuKD2wIuMLXCk MWQz/Zmciz53i8/WicTHnGlWU7xKVH8m ZIFEzzhtcR71NVkHrMVuESKuJp1fm3lR 6FgC4KReLduGXcqDVCZNwg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAbS4LSbmieIk96i3AK8IEZXlLRdM YLoEp4QSRMrdtJ0VcZKsjHWF6g9Q50T/ 1XKBxY/+6j71//cFRi1cViqoNsPd3LEb txWa3Jx/1HBINOkAPdHMBc3SQQ5qEIar PObq98RLnQ22ske1xZtbzNRZogPNi7zM HwQ3GYf0sUVCnYTmv8en5yYfSh8mY9Ez 8okorK7UN2RlHx9B8S5DRtBphO61mSI8 MXRj91aC3iRTbYZndTWorVkj4UvCBBr9 qh7oNpRbprIvAmRzRK6IaxpYDS6iGtSq 05WbGRtQRXkngOzFZO1BBDHgj9Agtx2S HBxAKuX4wF+w8I8xO7m5z0MOIF0=
+. 1814400 IN DNSKEY 256 3 8 AwEAAe1udmhwaWyg6PD00YReHeT6nYeN hKAy6oKMc7X5NXXJq6baVZT5p3AnFm7B 0ReT1dOpOjZuArydSdASupCQwRPVja+m 8PQSaAdThTifIBbP0Y2TeeA3yfGbHubo scMezJAXr7FGDQ8XIfyUYBDBoDaINi6j V/oI+aSnwm0uWdxHqfonzioTYenuTsax OGLYYuCMn9soxIuXatO53MpqfsJgh7UK FDCDGKA9pFts1vTQdCMPGgEsuYHjsYF0 /kUoRAQJM3jh0t3wE3JQ/HjFIyR37NCM w+i20b1v6/Xo86gy2nhXq2BopawLPEh0 Fv2UCCI7f8k2JvvF2o9udq/N+FE=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcgeCFZiwMD0S6TVgSK3Ob/MKFo+ dHYeNgoOjxH3JoxV09WShBXID/LwKs/e sYAIzTOBB8Fu9IDoVuQe5prOEcGZRp6u zF7JLnqrgOzoaSPRmKsvXh8DfipA56qE AqNwYBCqzlbGIGjHOQ8t+Xonp4fSBYbN MLmh7o8+u55k7PxemKTCiAnY7Bjx0g3m J1HuFvQXDDdvyletuoS3NKqrkjqdOHRB 3p3GKrKgw+zVb05UEyotCEKjqqi+BOmg cm8rmUIeaNQREjxrUyr8/Ry67h5NJLfq LAqAnSePf1PyhQcl8XjGHXN7w1tLLyJu Gy5B3zLSW1P6jIjon77PSn+cXi0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAfX4eSO5BEPXggvx4jL5HkEjbA+5 QD8Acnh4wOHNv/OJX4QhKnpoMDOrpp38 n0Rgcr8qZ0XUqiJWl8eEH//wrQDHoO5/ JHRmibVrcA6UqfhUa25D1BfeMtTgMCSS +W3/heB/YhyXOJtOdWDmJutDY1nfJUSK rF6XwBj4us7U5jvwXwq+l36AeI4q7I87 Std0GNgRdA6/cJtApPoGv/0oNE0iNTyu IBcAR+AeWu32+JavhTycmtMfZ+KOIShq uI9RHzChZ9yLikAT+gclJGLLlxIr6HDr SLChCG90/yvmc57dLn8qhRWRJtJeLT2V HSxeP7oREb6RSdkxET6OTf9GYHE=
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20171022000000 20171001000000 1867 . SLUfyBPUVaIp+yzafCz6VdqrSFGI8XA4 4Yh1m5g33CTjHLHYdtprsZbOmU1DkcJf zeQpHrMwStfDycnvwCbtN7QV3hzVIsOS +4npL6l1nn8ORi4x3dklHrks2rl/L+ju vsI1KE5dLuTEPLKxcrt0SRd+JsxhzewR WZwAxFFYVeXNWoKQeuFPH3BSeqC1hcJ9 p54vcoOYvwm4h+x+R93QZ6j3ePmj6wih x0haMVJ1tbPxoCYJK+LZoRynnWmhEm2p Wivw5TlYE5eGWPK2NYSO2te7cOH2y8Py taiWTE/VOpKEFJVHyXDQfffONLbTRFaV MCC4eDAVi4LTYt1gejgx+w==
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20171022000000 20171001000000 43863 . sWLCuaV0WZTEAIjXVuAC6jV18aQubTO4 Cs2wA9hLJQyPFIN+cKVrwMP0PXHrVNs/ 3k8UIBg/4BcgWCT7BbypiaaJjiXhKDxn hIk0oJRHzE7sGE5mvsKlqk4XBeLNnuCN NykshvAbpbh7lsFBqQ7JjtaZeRTu6JW+ CJixWFb9E9QYyJ3jfKDwLhiytpiWmr3Y ny8y4a2pOwYnuAiz2XzZP7I8dHXdPHaA sfitx1HSLpFwGUvfe3GdcAZRpoTKmtL5 gz4Uy+4HIawROTtnptir5OyAvCqeU1T4 BfnrMuYt28Zj1CzbF0H4vCjTyBWYC7gj GEehw4AutnhxoFlgFIFx3Q==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20171015000000 20171001000000 43863 . UUCE+pmAPxa4t+LyPxbpjtVEq7F2DU7d 5tGfvJcNhFmbgEcl/7/PvaoPRTHOtnKI J6zLb8068Q/Y/rziAgW1vjuvmmIScefi nEcASJAkczBMQLDzyqLYnOfhORPRQyTo MRG0fGnsw03+qjOo4lfsdqvLvzSzkiMe idplhFpQ65Wpc4ARKRc0AHzde9rdD9uU BAFAUg23A8Z0p6+WuVi5lwS/TCnFbT/Y bBx9/wcOK1Be4nzvtR6KiwxZVk2DHVyV 8DqgGCQxLWbd/4sIftHG+URJlXi1ZGeG eFQLs9wY1ES5fSCdot3yRfAGRnLzBxN8 2SoCIGo7VfhRK6/Z00/K8A==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20171015000000 20171001000000 43863 . WhLjFUV6O/CeESF8322BZSdt32YTh7uh H2wJNLJnvtkJu/4QoPGx3AvgiLquC52m r3PFFcD6ZPC1iqKRieLqEfyH0B8iS1Oq iADBUjdLC1e5mKsEc+bwb5P5heaWdu6g FrhoPPL98QtswwXFfsmhOwoQomplIDlu gNLUyCjfvCSjX6raEKbuBn6pn65OlT4s sZh6nCT9YOYma5QL5FxFlaFzUfJn0qGQ 2k341M6sS6U84rhiFKWyRtSqShPzCnfB Q06Rcx47fabVpcUJ2rS1DE3jc9bIszeZ n8IK4TlxkkQ6CbaahKAUeXQ6iVdAEaZ8 Ut2XVbPlTnkCGbBQQbismw==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20171015000000 20171001000000 43863 . VfbolsQWe3CmwrnWRsdmx2k5gaQmSt7Q oKv6b11iLn+oY1gpQL9fz4YRU8z5HzTv XFvgtdLDnN/7jmo6ECgKU43ToOgRlNBv nu7FijQ0iBj34Lxl0D9geAJ12lakVHHG pbzokVSTVyIduoOhhMT5ZEPmQ3e3ru1B xArn4MFUH97cWnfFkGobDz+nas1Z9E0i kZ510rYpKB8MVyhO7l79txsxwK8Y/ut4 4jg6bfGTwTSk9ILWalsSMYy0CVkQHUgE Cm8MracEG7D8TTBWipvoT5WD/qA4usyx 7nXUeQH/wM5q/9U4iRY4Vf+1iPk3d6+h gK/MteVeDPZYTOS+IwzWVw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20171015000000 20171001000000 43863 . NqLOBYw5S0U6TN0X70UZv1V45vVbiDSp 1qpD4Sb+dNaCRuz9xxAWyQ4hfAcRh1GB 7L9w5HBviGwqfHQYZQqWvqEjXl5Dcgu7 h0pGQnNfEh2Eq/v16MEVf9XSR4C6ngK8 qtjigNahjsVNYz8COoooQuP5plu1LDzs myTcldGM3EbW9oYEZS5gpPXrMjFn/j7Z 6q87y/1uhJh4hiv5THl5kL416YVQzZdK fVS+oJxJe2B70H7PIOtOuKD2wIuMLXCk MWQz/Zmciz53i8/WicTHnGlWU7xKVH8m ZIFEzzhtcR71NVkHrMVuESKuJp1fm3lR 6FgC4KReLduGXcqDVCZNwg==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20171015000000 20171001000000 43863 . js4jobso3JzElwlF+PAw4W9No9vgyaJm l9EdFtRCdGyixA6bLL+mAdZESbpMvUpR 4t+DBd+zbOeHlJUnmtTib/eU7stYM1LT T4s1UZUh7HZh1RYQ/ujZsn48+eKCShKG OGdidugQyIreLm55qvj51yro0Ie03juN jag88naOfvS5mbq8OmN82apeGxjwiISG u+VegiXchOiDeHZAT/dxlXF2lJQ+fodN pmGrMo6y5jm1EW2hpDNqW3DcM0rWJ1fY AkiBxiwRnkpdR5ckqfVXT18xKoZfEy+3 bXbdh0Cd40IipkuwjmOh5ZXGma15CU7D BgGJzmP/60pJdMAAD7rjsg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20171015000000 20171001000000 43863 . WhLjFUV6O/CeESF8322BZSdt32YTh7uh H2wJNLJnvtkJu/4QoPGx3AvgiLquC52m r3PFFcD6ZPC1iqKRieLqEfyH0B8iS1Oq iADBUjdLC1e5mKsEc+bwb5P5heaWdu6g FrhoPPL98QtswwXFfsmhOwoQomplIDlu gNLUyCjfvCSjX6raEKbuBn6pn65OlT4s sZh6nCT9YOYma5QL5FxFlaFzUfJn0qGQ 2k341M6sS6U84rhiFKWyRtSqShPzCnfB Q06Rcx47fabVpcUJ2rS1DE3jc9bIszeZ n8IK4TlxkkQ6CbaahKAUeXQ6iVdAEaZ8 Ut2XVbPlTnkCGbBQQbismw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20171015000000 20171001000000 43863 . VfbolsQWe3CmwrnWRsdmx2k5gaQmSt7Q oKv6b11iLn+oY1gpQL9fz4YRU8z5HzTv XFvgtdLDnN/7jmo6ECgKU43ToOgRlNBv nu7FijQ0iBj34Lxl0D9geAJ12lakVHHG pbzokVSTVyIduoOhhMT5ZEPmQ3e3ru1B xArn4MFUH97cWnfFkGobDz+nas1Z9E0i kZ510rYpKB8MVyhO7l79txsxwK8Y/ut4 4jg6bfGTwTSk9ILWalsSMYy0CVkQHUgE Cm8MracEG7D8TTBWipvoT5WD/qA4usyx 7nXUeQH/wM5q/9U4iRY4Vf+1iPk3d6+h gK/MteVeDPZYTOS+IwzWVw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20171015000000 20171001000000 43863 . nkmiASU/GeSZEoCScItfXTCRJHCkCGsr fYarRDxNDH2hegzh2XHT2l/bEhDoaV+b gaPNVGg/nTcKsQ3zUKg9ufhnpSNE3tMd zlo5PPpPz/2+8hKrd4m7n8HwKilVaruk FC9pTCu46QzBCiz2TDj87ADf3vaamJVj iEmZQEiVu3TDUi1M3wjj6E7Pbl1i08AF ejYcYXex7gjen4kyQhLZ9CpxgdJb8n3U Z6eXRjBNo8r57XZr37vDGlVDcQB7w2sV APFsiQBNTQyY+t+B8T2AK3JPK7ipn08T gdqrO6XhkLalMZo5wF0UNmEHTOKznt+Q Rt1eEgUReLPxhWDz4tQnxA==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20171011000000 20171020999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20171025000000 20171011000000 43863 . j84QURP9YH3d3RDmH+1/hTcF3oDAwQx6 1aorcrvDubtKljRL+fD/lPpdTDqa0IT8 tQv+N4AyHe2M/CguEoQLe7//Mckn0oxB 2fW8FRsXBg2ROroG3LHo1Fr7vMwJdT1u s+ybnUUO1yIwmC9WgDZ7X0alRtsV1kqB w18tcpsgoFR7S5pyO6iQ15nJCP9+dXNT 0OMIR6IF9jAxju39zSMHd8sx6WHvYgYK gEBpxHBqHspqupopbaDFvLGyYM5Sl1dQ KBI0HxVTPkL5xg36Ds9mK2AImWltHRMw oYdTOCvzrvXIH8U3z1gv38dMbvyHBKyE qCKQmG5bSdlYZEN56Ds9TA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAbS4LSbmieIk96i3AK8IEZXlLRdM YLoEp4QSRMrdtJ0VcZKsjHWF6g9Q50T/ 1XKBxY/+6j71//cFRi1cViqoNsPd3LEb txWa3Jx/1HBINOkAPdHMBc3SQQ5qEIar PObq98RLnQ22ske1xZtbzNRZogPNi7zM HwQ3GYf0sUVCnYTmv8en5yYfSh8mY9Ez 8okorK7UN2RlHx9B8S5DRtBphO61mSI8 MXRj91aC3iRTbYZndTWorVkj4UvCBBr9 qh7oNpRbprIvAmRzRK6IaxpYDS6iGtSq 05WbGRtQRXkngOzFZO1BBDHgj9Agtx2S HBxAKuX4wF+w8I8xO7m5z0MOIF0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcgeCFZiwMD0S6TVgSK3Ob/MKFo+ dHYeNgoOjxH3JoxV09WShBXID/LwKs/e sYAIzTOBB8Fu9IDoVuQe5prOEcGZRp6u zF7JLnqrgOzoaSPRmKsvXh8DfipA56qE AqNwYBCqzlbGIGjHOQ8t+Xonp4fSBYbN MLmh7o8+u55k7PxemKTCiAnY7Bjx0g3m J1HuFvQXDDdvyletuoS3NKqrkjqdOHRB 3p3GKrKgw+zVb05UEyotCEKjqqi+BOmg cm8rmUIeaNQREjxrUyr8/Ry67h5NJLfq LAqAnSePf1PyhQcl8XjGHXN7w1tLLyJu Gy5B3zLSW1P6jIjon77PSn+cXi0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAfX4eSO5BEPXggvx4jL5HkEjbA+5 QD8Acnh4wOHNv/OJX4QhKnpoMDOrpp38 n0Rgcr8qZ0XUqiJWl8eEH//wrQDHoO5/ JHRmibVrcA6UqfhUa25D1BfeMtTgMCSS +W3/heB/YhyXOJtOdWDmJutDY1nfJUSK rF6XwBj4us7U5jvwXwq+l36AeI4q7I87 Std0GNgRdA6/cJtApPoGv/0oNE0iNTyu IBcAR+AeWu32+JavhTycmtMfZ+KOIShq uI9RHzChZ9yLikAT+gclJGLLlxIr6HDr SLChCG90/yvmc57dLn8qhRWRJtJeLT2V HSxeP7oREb6RSdkxET6OTf9GYHE=
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20171101000000 20171011000000 43306 . prNbOqQOWhxL1tTDnVFk0IiHBmFZfYGf L/1jC0gcC7r20aODKHGXsxzNfacx+ohB Jr1b8s5UOk9Y6L7R9FwYNDAGC8W80F1A qp7d6CKKAUcnmiGYLbr/YaxZa/dLRLSM MYxSbKjCu7dEtZPp1RHhn4Mm9F9LatdF wP+Gt/tsKM41Rp2yaev7MyzlQmzKeRuk q4+nOOlGoreVCxr7gTedX2aOAlU0dy7V 90UKkmoQT2/qG770n1nT6SZLO+3gzahJ 4qog+N4nj3v33YLVCep6sbQApQdrIScU cqCQLh8+b+ZZSwFQ5exS9EQ3Vn/Qq6Vv oHvvdgDhHN5it3hN+KwaAw==
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20171101000000 20171011000000 43863 . RnQlmuJYsevQtDCaROBK2EhRYg4a6VQU Mml2ACLppliraGaCYrZ3Md1QG2NYx3jv LrqYLXCPOL1gYTQ+cviT6vzltvPqaATB oZvqfoxTlv0Ue72SZixxFJpphv/KkxaI Amer++W3j24AcsYFJRtgMejjQAoZuoou GfYiF+STZKUv0atrfA+ff1Bccc8qPB8A fXW9W7VdAQj0oizKDUr2++XaAL7w06pl 9EEK50JhhFiQT7ycxJdwXwq1quQCxAYK hlGyOGcolAbSnyMMqSvAnuEmdQ4xViLn aA3qSlm+MwCAQof6CRZKQykMIubo2ADf qcjLwxIsiezhnFs2GPV1Tg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20171025000000 20171011000000 43863 . rpQpv1g2i6O3oeePRi5/rFU3IxV3mn5v fmHgrmx90DSrD+sIqJmZccH6HywkvB3/ oNsYlub6X6+ynHaKXLwjEcqF5W1Vx9+d 8mY7Bb5ncT8zICJA8ey4whQ53xaBZiMT NI0IsH49QfO9ni7oS2BXzZfzT3VJFbWW xTa/n2Ptw54IlODyiRWiZa6pmMPvSC0O 3CpxD2nKN4wbmR8CicyVfqbBzqlb4et7 z630ydAVMKnm9lZu3o5ILI+TzYww4dNO diUBUR3H4ui/U6x390S/ymdgn05PL+xs nHq1/yAjrbDLAKlgr7mxTnxUMXq+MIPR aMeR0WHZMjl1jZH22Cjz9Q==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20171025000000 20171011000000 43863 . DhvuKaow+/abfok7XV/keFr6vx/5OBV4 P1k9XtNmg9BZ3E+7NRvyJUWj8MEqAKYX GhVbVU8L7ByLVOC7GBzpuJg1Ics8vP/H 5vSXJGfc4C1gGnPt2SvhcPrWTNKdFsT6 PnO7J7YWNzCJOvbNkT398vi20nZTF7Wq V8XDEr6sg50X5b3BqAncvnj2ZzIC9ONg InX0F/R1CxUS84DAitqHmwRpZ5Ci215G 0Ho1LPPPe7ENzpLRAtGNVjmk0y08+/re TcqoRPxV+1puXvFDr7x2y1dwE7U/EL+i BIM+mZmDv57BrqilmaEASqk6JjBb3jxS 13m6pS4AK7x6kvlEtWp6bw==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20171025000000 20171011000000 43863 . A8wCWy4vpVHqbcBeHinc1gzt9ox6FcRo kSLwswv2SRCAgG/U9iyZHT9fI3K2EjRL VdQycBQrEdp7TiXx1kfFYqvBkgN4IDBr F72HNr47O00reAQs7TAy4E4rtiNkqS/Y 8TJ+ifvaMVJhUGTGzNPOBJcvSRag3Lf8 slr5m53ZM7Cqjt+lV0yF2OAlb85Mi1VM 7iW1gX+OAIu9i6pxbDyJy7HKI6RVvZ/C 5f0dYlBz6WMEfT7CQ1ai21y6hrMZZMAv wRO6msou52aSfpcs9Gt56R8TJ8ZAG4mx wHSDJESBEL8HZPrnwDTUkgWtgMasvLa1 7Y9b4J0QfyWKFJqF9qCAJg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20171025000000 20171011000000 43863 . j84QURP9YH3d3RDmH+1/hTcF3oDAwQx6 1aorcrvDubtKljRL+fD/lPpdTDqa0IT8 tQv+N4AyHe2M/CguEoQLe7//Mckn0oxB 2fW8FRsXBg2ROroG3LHo1Fr7vMwJdT1u s+ybnUUO1yIwmC9WgDZ7X0alRtsV1kqB w18tcpsgoFR7S5pyO6iQ15nJCP9+dXNT 0OMIR6IF9jAxju39zSMHd8sx6WHvYgYK gEBpxHBqHspqupopbaDFvLGyYM5Sl1dQ KBI0HxVTPkL5xg36Ds9mK2AImWltHRMw oYdTOCvzrvXIH8U3z1gv38dMbvyHBKyE qCKQmG5bSdlYZEN56Ds9TA==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20171025000000 20171011000000 43863 . V7uXPeHovcCZXfST0psQLPNfg22S1Kq2 Vzk5wihwlwIAhAYOjVDFvFhItZzf9yQ3 WYzJXfDOh4oy8bb2WKMJkOTlA+5q78cX 6iWlp3eqx57Vs6J7n+FlvO4mVdod372+ yRh2XhjKwfTK58dhSm70cTtVP4wxcQ5d ZjPvhK79LJuHU8WbrputA0W9BSv/in3L amf+wYEsKEMEKZC1bsMBPWLDdlUyjaHQ mqxR+UxODtAzl1JLXo1T3dNKeqspbIZU IEhcKSUuFGF4sqYPhPh77wc5cD4jXJcY /qgSDvjUtiK1cyFDhw4JWK/rRsisxgt0 +wIN8FVVt0TCCZFVmC6BfA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20171025000000 20171011000000 43863 . DhvuKaow+/abfok7XV/keFr6vx/5OBV4 P1k9XtNmg9BZ3E+7NRvyJUWj8MEqAKYX GhVbVU8L7ByLVOC7GBzpuJg1Ics8vP/H 5vSXJGfc4C1gGnPt2SvhcPrWTNKdFsT6 PnO7J7YWNzCJOvbNkT398vi20nZTF7Wq V8XDEr6sg50X5b3BqAncvnj2ZzIC9ONg InX0F/R1CxUS84DAitqHmwRpZ5Ci215G 0Ho1LPPPe7ENzpLRAtGNVjmk0y08+/re TcqoRPxV+1puXvFDr7x2y1dwE7U/EL+i BIM+mZmDv57BrqilmaEASqk6JjBb3jxS 13m6pS4AK7x6kvlEtWp6bw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20171025000000 20171011000000 43863 . A8wCWy4vpVHqbcBeHinc1gzt9ox6FcRo kSLwswv2SRCAgG/U9iyZHT9fI3K2EjRL VdQycBQrEdp7TiXx1kfFYqvBkgN4IDBr F72HNr47O00reAQs7TAy4E4rtiNkqS/Y 8TJ+ifvaMVJhUGTGzNPOBJcvSRag3Lf8 slr5m53ZM7Cqjt+lV0yF2OAlb85Mi1VM 7iW1gX+OAIu9i6pxbDyJy7HKI6RVvZ/C 5f0dYlBz6WMEfT7CQ1ai21y6hrMZZMAv wRO6msou52aSfpcs9Gt56R8TJ8ZAG4mx wHSDJESBEL8HZPrnwDTUkgWtgMasvLa1 7Y9b4J0QfyWKFJqF9qCAJg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20171025000000 20171011000000 43863 . jou6HluOgT2jv6k74V4tJZcstOdYnTl4 kWV+x4NZbYYCNrVoeNf91yeX45ABSIB1 PZZppw9aR27m6GiOqQ9I6jSWmpVV99Cy u6KbsK52xc21S4dVhamv47wz8o82JtqY FPYz2GhidDKezf944ggoti2lTteeZl+M zbxpEq5Oq/9138wawGEePBuWsMp/ObNe FQycIfICMkm7Ng0qww/0jiwQB2LvYvak x6O1c4C45AYOsHt5U3DFu1XVbqDQKC/i WW2LOdn+RD94QRudjFxt1AVyn0UA55KP sUuoZq4eBQLY3Tp0QQPn/5XAhrT+iA41 PaBL/0kuNyhygHm+pmi7Cg==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20171021000000 20171030999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20171104000000 20171021000000 43863 . QooElIIDCY4Jam07Bii596BBCb2TiGUY TmbV60FaeBfBXWRkkT+mCuWe5PpBx3/A gdMbB2/OrvDi7CrGOehWyiT4gUkgAO37 BgVG0JEwEt5S4SOCDRz7lUgedB1KdoTw FhWTJDJSXZfQYVE3ksh9Chi+ySUaoPUk dEk6MTw4F1CEOMAXJ+ST9sG88kg6E+YY 00GmZGR32ALaCx07FOaUE6JESbhRdF92 6NlS+VVMGw/v5B0lxvlyneQJWZ4mBDGL MxvNhwwC6ElwJBO09sgg1UOP18A1zpkn dSInPzXP+qS+P63QuUlFuE+1DhfNP4WW 7ISPuytZyMIVSGB9b7igUA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAbS4LSbmieIk96i3AK8IEZXlLRdM YLoEp4QSRMrdtJ0VcZKsjHWF6g9Q50T/ 1XKBxY/+6j71//cFRi1cViqoNsPd3LEb txWa3Jx/1HBINOkAPdHMBc3SQQ5qEIar PObq98RLnQ22ske1xZtbzNRZogPNi7zM HwQ3GYf0sUVCnYTmv8en5yYfSh8mY9Ez 8okorK7UN2RlHx9B8S5DRtBphO61mSI8 MXRj91aC3iRTbYZndTWorVkj4UvCBBr9 qh7oNpRbprIvAmRzRK6IaxpYDS6iGtSq 05WbGRtQRXkngOzFZO1BBDHgj9Agtx2S HBxAKuX4wF+w8I8xO7m5z0MOIF0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcgeCFZiwMD0S6TVgSK3Ob/MKFo+ dHYeNgoOjxH3JoxV09WShBXID/LwKs/e sYAIzTOBB8Fu9IDoVuQe5prOEcGZRp6u zF7JLnqrgOzoaSPRmKsvXh8DfipA56qE AqNwYBCqzlbGIGjHOQ8t+Xonp4fSBYbN MLmh7o8+u55k7PxemKTCiAnY7Bjx0g3m J1HuFvQXDDdvyletuoS3NKqrkjqdOHRB 3p3GKrKgw+zVb05UEyotCEKjqqi+BOmg cm8rmUIeaNQREjxrUyr8/Ry67h5NJLfq LAqAnSePf1PyhQcl8XjGHXN7w1tLLyJu Gy5B3zLSW1P6jIjon77PSn+cXi0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAfX4eSO5BEPXggvx4jL5HkEjbA+5 QD8Acnh4wOHNv/OJX4QhKnpoMDOrpp38 n0Rgcr8qZ0XUqiJWl8eEH//wrQDHoO5/ JHRmibVrcA6UqfhUa25D1BfeMtTgMCSS +W3/heB/YhyXOJtOdWDmJutDY1nfJUSK rF6XwBj4us7U5jvwXwq+l36AeI4q7I87 Std0GNgRdA6/cJtApPoGv/0oNE0iNTyu IBcAR+AeWu32+JavhTycmtMfZ+KOIShq uI9RHzChZ9yLikAT+gclJGLLlxIr6HDr SLChCG90/yvmc57dLn8qhRWRJtJeLT2V HSxeP7oREb6RSdkxET6OTf9GYHE=
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20171111000000 20171021000000 43306 . shYCeDVHCCBc+HDxr3Sdl1IlnbqpqR+P BDt1j8U37iGqHMfa9tAHUtUU5a2hlDbY N7xBPA2ZWJwksW1MGhgJO9bDrK0Pi1MH mfk1S2shqOmz8Z2VrDaO+RWzxDDh/b0R 0tVrcVrYOPR7C2QD0tsjhxDFZlE2TC9I 6WIXllAjOckxVqKidjzbKmOGgvlNHYJg uU333Tg+clWLlJVTghwHsTe7lZ6BiVqS 7frKNg2CaJg/XKkqH8mNkiAjfc8hc4Wo Yr3fDklX5p9leylrkq8TIYA6jTHK/v/d wUpK7mevN3PViuB+CSmqIGyHRTqbajBl euN9iRNAAaAHGyXo5jqqkQ==
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20171111000000 20171021000000 43863 . Yz7jMz1gE04oN+qZgNTiWABeemxA5+V1 QkWcIVvHqGOHzoFllKVIGoKu5MqEINXQ qJ3Mta5stkDHHQqbSGVAf1zwZwt/rJ/7 37Vi+dfP7rVyhRiyqr7blXQU9aOGXirK mDl9QyiTos3O3XJzu62Tlf1KeotOa0bO UAKN/Fo3u/9qLQT1KAQ0mmcuE2hmXE9f 1oJgmL7C2cduMhPIy9BbpSuF1KEWwO0J KrXggSwVV2Sj5C/LAHuAQOzDjPIhsQQw sYGiZLnY2A7GIm9jNfFrlidrgM9awNvK O1k2owB+Ocf1n0GIMsq9d/PkFdqfrDh3 rL/fjtbcHoWnvAK+ZGeXAw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20171104000000 20171021000000 43863 . fINfuCHxhSG/AkiA0+qA+9XixCDk0vRD W75jOQdvgrSCb5KpGcYCEYUTf4gmKB3A 9uxkTkLnozBXRr0RiYY9Cyzm4bWgyMo3 5QIY/zr78Ovtw4/DHobM3Pi2ZJuXPSk2 HfOs8paP9xoFvc+56oU4C/uGdbb2c45G Fenet3RDZEIEIWODChQn6Fr6Nf4NTrQV Y7oMggzDnNXpjUPHwcywzuBsd55D3rJc 80IwMGvaXzIh9pjth/i92WfiVk01pfM0 B9+uBQAjeF7VQXVH9iTpdiErm6jX0fUD M46wZQQgW63q81lGGeXgyU5QU/KchqL2 ZdwOUGJzIFWLZIh9C0efdQ==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20171104000000 20171021000000 43863 . o9EjGTFdfjfKCsZ2gd++IKzw1PjLeOLN 9ImHCTfPt228hOIToC46/JC5kNlFUpbT 5ojeEBqgQYHASBk9iSrOIBTL6m4aFReJ a+dl+xWHc9+hAXi/7ygZBrQ+FHQ9rF7t wIHn+EQPCjw29yxxgyUGsOE7mbeU3BX3 +EbPIdxxsldq3lasHPPOQVJPazxAVMa8 PTZhNMTzVg2s2yxwZjHfyINwkR0SyUub bEW9g47U1c0vspJi+1gsjfq9Rls3Tzta cIk6n7vICYx9lrGEUrBGktO2ODtiOdXu ffL0z/zOVl8e3rzNRm/PRzyWpQvIqis8 3ehJq7S1OaNV7XqE9oetdg==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20171104000000 20171021000000 43863 . jIFv95ONRDAGEW5HuwBA5wC+vIRb6p7u mTuDoY3IgJPP1etrVi7bf/HlTL9Y/6O3 DXrqq+WaBN0MC7unImWssydpMHN/3U8e HDPybfeHC+XqoRLnz0TJ3Ue24tuzEU5Q LyqumAHyfeEAdIdIIff+RKqTLoGbY3+j W8zBsXbWe5c4yc5ywiGcheCIuJXzJEQf vxFtdyHdkoWgNkK8P8h5SVhZiocWOjnM pjbxVOnehVJtqBfwCQNGU+0ZuSlUUD9D EG0U7D5K5VndP9ccyEhPk9nLc37cp9Ho PRmxPb0y7nW+4L0oW82veAz4XFab+lYM 0oiRDz3Oytrxp1dwUATC6A==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20171104000000 20171021000000 43863 . QooElIIDCY4Jam07Bii596BBCb2TiGUY TmbV60FaeBfBXWRkkT+mCuWe5PpBx3/A gdMbB2/OrvDi7CrGOehWyiT4gUkgAO37 BgVG0JEwEt5S4SOCDRz7lUgedB1KdoTw FhWTJDJSXZfQYVE3ksh9Chi+ySUaoPUk dEk6MTw4F1CEOMAXJ+ST9sG88kg6E+YY 00GmZGR32ALaCx07FOaUE6JESbhRdF92 6NlS+VVMGw/v5B0lxvlyneQJWZ4mBDGL MxvNhwwC6ElwJBO09sgg1UOP18A1zpkn dSInPzXP+qS+P63QuUlFuE+1DhfNP4WW 7ISPuytZyMIVSGB9b7igUA==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20171104000000 20171021000000 43863 . azjrdB6MPXt1MBU4N24js2WxcHllrqPZ NCTkEJXqHgdTrkUIHwkaNvIqbqpPQEfk 3KSmVzelKQ+IvQyi++skRUJdXgo8AQ// iM5P06RoNjIorjMCnF+ttYK6LCy7Sk0A d/r+nYsn+uVU6WF8/SKzb4CXntv/pIDr 4qdIF5zt0W3ear2/m0q8k0ET0abncL3U QJKU/1y9IC3Xe9L4z8Ivp8h1pQk0+kda TW7RYNao4O+XOsT5FFARvLCRUIu+J2yC NUwqa9qmfwd2a0HFxQ5xmCAD6sH7n5gp isEBlg6lp7RpuqzgMpQU4PzbH/yQ6XJj huU2sB4zkYogd5F1f0b1sw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20171104000000 20171021000000 43863 . o9EjGTFdfjfKCsZ2gd++IKzw1PjLeOLN 9ImHCTfPt228hOIToC46/JC5kNlFUpbT 5ojeEBqgQYHASBk9iSrOIBTL6m4aFReJ a+dl+xWHc9+hAXi/7ygZBrQ+FHQ9rF7t wIHn+EQPCjw29yxxgyUGsOE7mbeU3BX3 +EbPIdxxsldq3lasHPPOQVJPazxAVMa8 PTZhNMTzVg2s2yxwZjHfyINwkR0SyUub bEW9g47U1c0vspJi+1gsjfq9Rls3Tzta cIk6n7vICYx9lrGEUrBGktO2ODtiOdXu ffL0z/zOVl8e3rzNRm/PRzyWpQvIqis8 3ehJq7S1OaNV7XqE9oetdg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20171104000000 20171021000000 43863 . jIFv95ONRDAGEW5HuwBA5wC+vIRb6p7u mTuDoY3IgJPP1etrVi7bf/HlTL9Y/6O3 DXrqq+WaBN0MC7unImWssydpMHN/3U8e HDPybfeHC+XqoRLnz0TJ3Ue24tuzEU5Q LyqumAHyfeEAdIdIIff+RKqTLoGbY3+j W8zBsXbWe5c4yc5ywiGcheCIuJXzJEQf vxFtdyHdkoWgNkK8P8h5SVhZiocWOjnM pjbxVOnehVJtqBfwCQNGU+0ZuSlUUD9D EG0U7D5K5VndP9ccyEhPk9nLc37cp9Ho PRmxPb0y7nW+4L0oW82veAz4XFab+lYM 0oiRDz3Oytrxp1dwUATC6A==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20171104000000 20171021000000 43863 . R0doO41QT7TCbRhaJNymCye+QTEbSMKY XPE/hp62sZm4zQaXuALDdMz9L+4dyDJ0 sp0HK+1hiLZQeWxG36gPMXcsfBB63wlv 4yWyqJWlzbRMJjI60BAhqaYDVz/6x3gi Y1zgzWtw0NFcrXJLysS5a8JCzAWMqW9b IITITWqz8Cv6WA9qqSxHI7HdQkbSg+0t LWq0hECZmRsZUrJviqCRs7Mf2rQyNKBE 8637RRq8MFBW3GKuK6Z3eXSn3fLsbWQd 3Mvcr8lmBAtlpvbZw4JZdvL9xwhqxMBr 5kkHL1zeGVJx/8ibrXZTMvlEcrwNV5ap 1fjJ8Ecu7o/Bits0jYC3Lg==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20171031000000 20171109999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20171114000000 20171031000000 43863 . pDeid0DvLx90cUWzAnUqCLV8XehmC5Ks VyGws6GCHwW4m8/9E8XXKcQFMVITO2yn MbOeIFs/OXMG+TnOuJfckXAyais6/TxO iiQg22r1ZdlZtv3szzoirJl4/+HXqCuU dm6esFje5djjrwAW82dHkt/FTu+Kly5x 0pwltZ2Qz2JvlIypExZoI6OxedNsv2v8 IMllSsTEJ2/w28ALGNvpyTN/LAX0cP1z IAOHd5r3UMraMS9CyRqVXNESj3g2Mhpi JM51+funl5ZEE8s4TzYjuqeQqP92jWfZ 6ViiK6sKGcM63+TRMVLW8DE07YDHREu0 JUsSPwtrD4hpHbfXSIiNEQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAbS4LSbmieIk96i3AK8IEZXlLRdM YLoEp4QSRMrdtJ0VcZKsjHWF6g9Q50T/ 1XKBxY/+6j71//cFRi1cViqoNsPd3LEb txWa3Jx/1HBINOkAPdHMBc3SQQ5qEIar PObq98RLnQ22ske1xZtbzNRZogPNi7zM HwQ3GYf0sUVCnYTmv8en5yYfSh8mY9Ez 8okorK7UN2RlHx9B8S5DRtBphO61mSI8 MXRj91aC3iRTbYZndTWorVkj4UvCBBr9 qh7oNpRbprIvAmRzRK6IaxpYDS6iGtSq 05WbGRtQRXkngOzFZO1BBDHgj9Agtx2S HBxAKuX4wF+w8I8xO7m5z0MOIF0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcgeCFZiwMD0S6TVgSK3Ob/MKFo+ dHYeNgoOjxH3JoxV09WShBXID/LwKs/e sYAIzTOBB8Fu9IDoVuQe5prOEcGZRp6u zF7JLnqrgOzoaSPRmKsvXh8DfipA56qE AqNwYBCqzlbGIGjHOQ8t+Xonp4fSBYbN MLmh7o8+u55k7PxemKTCiAnY7Bjx0g3m J1HuFvQXDDdvyletuoS3NKqrkjqdOHRB 3p3GKrKgw+zVb05UEyotCEKjqqi+BOmg cm8rmUIeaNQREjxrUyr8/Ry67h5NJLfq LAqAnSePf1PyhQcl8XjGHXN7w1tLLyJu Gy5B3zLSW1P6jIjon77PSn+cXi0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAfX4eSO5BEPXggvx4jL5HkEjbA+5 QD8Acnh4wOHNv/OJX4QhKnpoMDOrpp38 n0Rgcr8qZ0XUqiJWl8eEH//wrQDHoO5/ JHRmibVrcA6UqfhUa25D1BfeMtTgMCSS +W3/heB/YhyXOJtOdWDmJutDY1nfJUSK rF6XwBj4us7U5jvwXwq+l36AeI4q7I87 Std0GNgRdA6/cJtApPoGv/0oNE0iNTyu IBcAR+AeWu32+JavhTycmtMfZ+KOIShq uI9RHzChZ9yLikAT+gclJGLLlxIr6HDr SLChCG90/yvmc57dLn8qhRWRJtJeLT2V HSxeP7oREb6RSdkxET6OTf9GYHE=
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20171121000000 20171031000000 43306 . V9PYEXyiskggl3JXqBuvAqgp4v6yQaJS swlbgCQSS84BI+nuJ+QgSpaRj89OS3rt We5VXJRe6OP4uD0bgfqggv8u4nLvjGcn bM/UWNb/u+cE5d7sX4P7/ANkSAHBiSC2 VVMwIYJwfQD//t+vMhZ1rjicX2S/p7tg zCn0VqG2RD8m2ZM2QfUrMWFB/hbVOftL TduQpPEy9N4GPo0VDOpZQ/Qn9/VcQIEu yVk0TRHkrCtkYEG7pngWKgyxQF3rVN+P enVrMuEVOdBjKImqDVvsgcvBSqgx5i82 BQkI2b1QvwKOj5QM2qSX38bNqpuTrkpR RgJV8DG7tI6ocHcEPtA0Zg==
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20171121000000 20171031000000 43863 . N1IXPtQQvfwqYmVPM4fWqewZjn9JXrav C88m44i6ng6UNIawuFC89Wy9eU7JrCxZ mgCCfBWfQUAS1MCj8J/AERRhm5Q6Wgy3 RCDKgsPO2ZmIfDELXd63e3VY6xUcjFOA 6eb5OuMeiL5yKL7EmPm7veIb5ggVWHj3 ViQTxICYpxiDdZqg03gtXSLrRObVLCi1 8We1RYsPbA2PgXbmHSgbV9P5ThVsla/C FAbynsOllBdefqrKtlQVBYwfHpZDkBKG 4DFkZgh8ugePcC4hF6zLajAH7ql8T9Jx ENQCZdlqkym4M2HUH0RWj74zR9qdaBix HpR27cnmMDhj5nkR/7Nyjg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20171114000000 20171031000000 43863 . KSnCPuiaTPZcw68L4x8jKg9Y3pABRzI8 yF7d0E/HFjXCp78Ika/qeD+AMKOhmgnr 5FbqeKQ+lw+M4ir81i4/GviGvovAu/ak 1M8+n8bTqfeoh071+xaOWP2orJIMCgH2 rdNaU8wJ5DdAmiT/wjWU4KqR1ghnTFaa GGSsWhbuECzbmxilgDN1Nm183w8g5B+B 0dAPNDXQd9oOeYIFb7lKnB/cbJtftgGJ Kmt8DEerfW3j4OKtcLKfvsk+bVRu5yPu /Vfz2yV6hqcIXguwTv5cswiy6gzPx7mB kjG6eiW9+WwoJBZoqkUZ2z7T8sUDDc1U Qodd9HG1pQL1iQBw6D5rlg==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20171114000000 20171031000000 43863 . Q2f22oIR9AT33oXvIJLO0dfO3wtMXp7W J9HVugc4XkY2unjuFjX5zsdTsx7UiVwp eGF8EFaRXo7LFQUdu2mqZN1dh/V44srC e5GGDsWrjJUXYZZbXxVGMni3RQNCzRgf 6o9zTJhl0Q/uQVjbRz3720J7C6dZY8ph qxt9cPuf2w6o5I7suCjFKGbdMm7X35Fb m2Hm52mKMdtWRfCpFSp7rLymINnJYyiP 7m5k+2LryfzX7/LUGKBQdaKB5eYysnss U6S0gMLNhCeDM0vBQeGKGa83WV2G1VAo 0jxySyFZJAWH54MJh65X8WiSz5tM/gZE FxfeQmnSJATDPzz+muWROw==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20171114000000 20171031000000 43863 . K336HxxLMygdaKlNABZQ1KTmVLCdFtCO iJSoarH3+hDnk5bJ9oPNcFP3F9maVomj KXkLCbOYu36w9OwsY2nSqkYef+lwiRH5 5S3ngGo71uQG4lyvX9xGO6/KDjhFdlwA TptdlemBNtbAa2AwHI/xXTNLeM/I17Yo 4l8z8lztKZSoQynBio09jk3tsw8b1E8c Vvbodg+JoLrIRY+7yHsfxZ5CASCtxpnp Re3UV/6OEZqnbdI5Z2kxBTzGPBY7MpYF S0rLRL3uiNaTRUtngfiGXiSvbM1jynQg wM9v3LwfuDyL+DWHyuJV6Zg9Nvs5EVZH PsgSbYKGjD0dRFtK0kUFqw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20171114000000 20171031000000 43863 . pDeid0DvLx90cUWzAnUqCLV8XehmC5Ks VyGws6GCHwW4m8/9E8XXKcQFMVITO2yn MbOeIFs/OXMG+TnOuJfckXAyais6/TxO iiQg22r1ZdlZtv3szzoirJl4/+HXqCuU dm6esFje5djjrwAW82dHkt/FTu+Kly5x 0pwltZ2Qz2JvlIypExZoI6OxedNsv2v8 IMllSsTEJ2/w28ALGNvpyTN/LAX0cP1z IAOHd5r3UMraMS9CyRqVXNESj3g2Mhpi JM51+funl5ZEE8s4TzYjuqeQqP92jWfZ 6ViiK6sKGcM63+TRMVLW8DE07YDHREu0 JUsSPwtrD4hpHbfXSIiNEQ==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20171114000000 20171031000000 43863 . fAkq3vxra8l/AN3x8kt8XYT0wXpPdESj M7FR7EKFAPCFL2BAelt3Hkfj+HPKqgCO 78iVXdIDfiFTzTIqG9JbUWtpTJF08P9R 76qFPoRRJ1vrmLC5d9LW5OG5MgGcFkQT OkKNN0LbKiE76W31f7ZFuFTZOHo/RJ1K vMG/ls0F+k29ZZHtyktLqr6tD5kkKJ6m QopzlejJTBkeFSHqQGFuJ/v76uSySa67 31cK0YiAYgIJBKmUP2cZu3jalYJ0JaTv eAJObpg/t2n0aKdclJHL/+2neqVCqTvw PYJ/eDYK/r2i8mioviysNOM8BKv2v3PO SBpKfaQ4P0tadHmqimZL4g==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20171114000000 20171031000000 43863 . Q2f22oIR9AT33oXvIJLO0dfO3wtMXp7W J9HVugc4XkY2unjuFjX5zsdTsx7UiVwp eGF8EFaRXo7LFQUdu2mqZN1dh/V44srC e5GGDsWrjJUXYZZbXxVGMni3RQNCzRgf 6o9zTJhl0Q/uQVjbRz3720J7C6dZY8ph qxt9cPuf2w6o5I7suCjFKGbdMm7X35Fb m2Hm52mKMdtWRfCpFSp7rLymINnJYyiP 7m5k+2LryfzX7/LUGKBQdaKB5eYysnss U6S0gMLNhCeDM0vBQeGKGa83WV2G1VAo 0jxySyFZJAWH54MJh65X8WiSz5tM/gZE FxfeQmnSJATDPzz+muWROw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20171114000000 20171031000000 43863 . K336HxxLMygdaKlNABZQ1KTmVLCdFtCO iJSoarH3+hDnk5bJ9oPNcFP3F9maVomj KXkLCbOYu36w9OwsY2nSqkYef+lwiRH5 5S3ngGo71uQG4lyvX9xGO6/KDjhFdlwA TptdlemBNtbAa2AwHI/xXTNLeM/I17Yo 4l8z8lztKZSoQynBio09jk3tsw8b1E8c Vvbodg+JoLrIRY+7yHsfxZ5CASCtxpnp Re3UV/6OEZqnbdI5Z2kxBTzGPBY7MpYF S0rLRL3uiNaTRUtngfiGXiSvbM1jynQg wM9v3LwfuDyL+DWHyuJV6Zg9Nvs5EVZH PsgSbYKGjD0dRFtK0kUFqw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20171114000000 20171031000000 43863 . GBgRHIHToQAAAn8VfGAPZwEO7YsOr0Kq wHtKcBlSdbj5y76sz/gfd0BE4ApyzCbL qo/RwOk/oxmTa7C/h2mRx6CXBzgLB317 LmAZWfm+/FxRZrHeaoH9/bTGeiwbhFJe aoq9wYa9D9oYBTUM+z9GFh72MHEc0Udd 7pPH+mEYdWO08h2Sx1lRpoi/ktLeH3Su y6ti1BNiH1pk9dm0JKduxmrlUTfmWmAv sWxTJihr71coNRHXXPavoH1nyKfc1rWc fmtrasozpZh1ddmcXn4YZAyxD/BZr2Vw 0GA+lcchfsRII3dPAOS2mz/XZoZxguf6 DEhdlERVGfmAOR2iyE9XZQ==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20171110000000 20171119999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20171124000000 20171110000000 43863 . Kg/cVNP2kAPJa/Tv5d3X8pPpq/oZgdh7 8NzEhZN7ZeHCcI39r4f6e40RdUGNzLI5 k2bZa9ncfCfA0au1s24Ctxl207X1ZDZ3 Q0jRIz6XgP3wI0ZoEzZwYbaBv0Tpuhr3 0Wg6nU2KJZ0avRhahKVAzmj164+JbFQg yDN+dOj0npJ45vdnK9kwos9ymdC/c6Ae uXgHoF188EpTs7xTtImBvNVQwONrduNs qkKfpsl+V1PdFl1f1NWDzzMrwKYGCyBt 3idLeRkqWODb0r1O3m8B7hrZnk5ASsRJ ERDMLIrfZvw91lYcSVzgV+GL++hG8oc+ 8pq68C1rJjBBHeqNodbHmQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAbS4LSbmieIk96i3AK8IEZXlLRdM YLoEp4QSRMrdtJ0VcZKsjHWF6g9Q50T/ 1XKBxY/+6j71//cFRi1cViqoNsPd3LEb txWa3Jx/1HBINOkAPdHMBc3SQQ5qEIar PObq98RLnQ22ske1xZtbzNRZogPNi7zM HwQ3GYf0sUVCnYTmv8en5yYfSh8mY9Ez 8okorK7UN2RlHx9B8S5DRtBphO61mSI8 MXRj91aC3iRTbYZndTWorVkj4UvCBBr9 qh7oNpRbprIvAmRzRK6IaxpYDS6iGtSq 05WbGRtQRXkngOzFZO1BBDHgj9Agtx2S HBxAKuX4wF+w8I8xO7m5z0MOIF0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcgeCFZiwMD0S6TVgSK3Ob/MKFo+ dHYeNgoOjxH3JoxV09WShBXID/LwKs/e sYAIzTOBB8Fu9IDoVuQe5prOEcGZRp6u zF7JLnqrgOzoaSPRmKsvXh8DfipA56qE AqNwYBCqzlbGIGjHOQ8t+Xonp4fSBYbN MLmh7o8+u55k7PxemKTCiAnY7Bjx0g3m J1HuFvQXDDdvyletuoS3NKqrkjqdOHRB 3p3GKrKgw+zVb05UEyotCEKjqqi+BOmg cm8rmUIeaNQREjxrUyr8/Ry67h5NJLfq LAqAnSePf1PyhQcl8XjGHXN7w1tLLyJu Gy5B3zLSW1P6jIjon77PSn+cXi0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAfX4eSO5BEPXggvx4jL5HkEjbA+5 QD8Acnh4wOHNv/OJX4QhKnpoMDOrpp38 n0Rgcr8qZ0XUqiJWl8eEH//wrQDHoO5/ JHRmibVrcA6UqfhUa25D1BfeMtTgMCSS +W3/heB/YhyXOJtOdWDmJutDY1nfJUSK rF6XwBj4us7U5jvwXwq+l36AeI4q7I87 Std0GNgRdA6/cJtApPoGv/0oNE0iNTyu IBcAR+AeWu32+JavhTycmtMfZ+KOIShq uI9RHzChZ9yLikAT+gclJGLLlxIr6HDr SLChCG90/yvmc57dLn8qhRWRJtJeLT2V HSxeP7oREb6RSdkxET6OTf9GYHE=
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20171201000000 20171110000000 43306 . TFUGDmiPAAixO77X5cFmU3Sx5eeMIdR3 8x3+fNGKkx0Fk4ZYsqLqI3pcihF7iNCM aOVqVkQkBrvWGd1T8SwPnDNoplS1y5Mw hA9uY+8npAKd0O4FOCalI73UW6aSvrRE PgjbUJO9BkAsj6QO0+ejdOA21XkCplm9 giQTRX26fWw37C0C7v75R69w8C9X0Dkj nymc3TmoHaCtsEWT+1MpreK/c/3obqNm 071jo8q6Pwo6acFjCRGu+qBWfdk0VuMS zoxO7VoI4DacE0xcfrd4gg+XVw7F45JV xeL+m3/EMOFqFizVWNmmIyAgYV1qCre2 nME7E+FdVgt6pS27DtphEg==
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20171201000000 20171110000000 43863 . agbQ597bBZCy0sTArlwjA7SS1Q5vrC4w tN3UPB2gTW+qysLL+S5gXMUfltyZ0MIa cJXDgxzXr2NkSn1zn399422uHyIvYmF1 +nOj6nvnYGbTUU0S69j0nUjdFxgulHsk HbrmK96N0nBIv6hvbresZ1jdzQvsRFhw v7s5x1hgKACz3VOMNt+n25feRrZanzGf YXr6jw+EXG2RjtH4tdu7uxcIsGlqsZwC F0KUTnpU2GX8Cj3RZ9HJ9oh+yQq1tJFx x+a+ystolVcQFPPJSFlTSc3KmBaP/uGI 3gJ/JvGqh3BvMGefkK6QYUCm6uBoc/V1 TUJf2X7qL7fORt7Lq/yXUg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20171124000000 20171110000000 43863 . pXHd/1pTHynpuquF1pHha2eFKyCu08mH 1+4A41rlauVzAibvKylmVvhxcwIA0CKI pjTh1STFFNc+7L61tNcLlOqUdAEwutNJ mUh2RtRFZF1kxTbRvZRGgUfL/+nD7M7G db3j6XsTQTdyKcytNc9+e1FH8lYV5E+s +aH6J50Y00XKK3pMxa5DkZcNTTzZDHpr WzZmFqvtE+ZefIawvjmX0Qol8fOfDeOi xEDwvwwWR4O/0PLdizQeYnzusxLL+wlp QxkbZ8tMYvG+vnhR/IW2o8ZddNeLU6Pg r7vjp61Oxb4lRvRzJdzIN1rGfvqq+eiT cMdbQ326wmTyhdxxkwkP7w==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20171124000000 20171110000000 43863 . l2h5+ktnsD5G+obFbqe3jTV96AowrBBk GFwAbwHuRujkp92YC+iY55CU4cTjozZI e5nYpgvE83ZHEewC44eN4mkZ0iGDkUYS 7ifh5QLiWTHaZDiEKxbqqiIay2c5jq6f uDmT9dJGin8aOwrWTaLHEXvX5VBAy9ym Q48kZl7rONOU3Amsxh2fdfW7qrl04RG+ XZVATT9mFuP8yJeXdFFr6HLb4PkscBXn IgJle0HaV2ZZ+RPUVigKwNlBnTDq4UtZ zZ7JxzLopxkxSSIZrmunCHRayVjCO4So ZBlYRjflAbZOWtG4pFwi+/fyY+y4/NGA fULau0RBP9phug4HGsDslA==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20171124000000 20171110000000 43863 . hTNNClNXX6qsoopTGHMiRGkBSVcR9W7F xNvy/e9nYbS79wcNWzYdweXp1AFtEm4t /l55IucR2c+n5Cm/i4ahQCrduGVDdKAy LfIm8tKf6MAMlEu6JxIWQ8r4z+dsmtml ATIffXshG3IPeS/htNAuxkwU7j23JW65 o321VNVvIGVJ1mPcyM1Z6n4xjvGmj40O Vj14vib/LA5ItQ/pWVl7U7Y8QoeMG45s FU8N4+TibInsJo8hnV3GM3t3B61QsziA gEvwvgD3gFRn4W1czvdxWQuQyyQVr8z4 Q3O78QHh5crzMgqlbnwrBr7q3a5+ltWb pAoEhCE7uccLmCIdde+bXg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20171124000000 20171110000000 43863 . Kg/cVNP2kAPJa/Tv5d3X8pPpq/oZgdh7 8NzEhZN7ZeHCcI39r4f6e40RdUGNzLI5 k2bZa9ncfCfA0au1s24Ctxl207X1ZDZ3 Q0jRIz6XgP3wI0ZoEzZwYbaBv0Tpuhr3 0Wg6nU2KJZ0avRhahKVAzmj164+JbFQg yDN+dOj0npJ45vdnK9kwos9ymdC/c6Ae uXgHoF188EpTs7xTtImBvNVQwONrduNs qkKfpsl+V1PdFl1f1NWDzzMrwKYGCyBt 3idLeRkqWODb0r1O3m8B7hrZnk5ASsRJ ERDMLIrfZvw91lYcSVzgV+GL++hG8oc+ 8pq68C1rJjBBHeqNodbHmQ==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20171124000000 20171110000000 43863 . aXTGM1DUbge++aq6a7SODevS+g/GaqEn wiyUpCzl5/mBo0HbIQSop4iP+j5A1Gv5 keW9+Jm23Nsa87KlsCkem5O25HseTo3c sIvMgEBrqalLJRC1R/lEVH1lmfz08gwd jWkc/zWgU9WiWVqC5fp+4rM2cIbtCMrt vCioYGTxPhQaEG9jm8RRMCS+mV5lPibY lq3f1S7iz/ZvzPPiA0JMshdBWOzRNCIH noTQ80eVX87yaBr9KWbbVezZa4jqP9yN +8IWwnvghcWBH2Tz7BujrqidRSyc1aVy bI1oCA8ykOcTqdJSUcl541ei72IVG0XO Mdu/qTOpqkw2d7aJBzRkqw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20171124000000 20171110000000 43863 . l2h5+ktnsD5G+obFbqe3jTV96AowrBBk GFwAbwHuRujkp92YC+iY55CU4cTjozZI e5nYpgvE83ZHEewC44eN4mkZ0iGDkUYS 7ifh5QLiWTHaZDiEKxbqqiIay2c5jq6f uDmT9dJGin8aOwrWTaLHEXvX5VBAy9ym Q48kZl7rONOU3Amsxh2fdfW7qrl04RG+ XZVATT9mFuP8yJeXdFFr6HLb4PkscBXn IgJle0HaV2ZZ+RPUVigKwNlBnTDq4UtZ zZ7JxzLopxkxSSIZrmunCHRayVjCO4So ZBlYRjflAbZOWtG4pFwi+/fyY+y4/NGA fULau0RBP9phug4HGsDslA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20171124000000 20171110000000 43863 . hTNNClNXX6qsoopTGHMiRGkBSVcR9W7F xNvy/e9nYbS79wcNWzYdweXp1AFtEm4t /l55IucR2c+n5Cm/i4ahQCrduGVDdKAy LfIm8tKf6MAMlEu6JxIWQ8r4z+dsmtml ATIffXshG3IPeS/htNAuxkwU7j23JW65 o321VNVvIGVJ1mPcyM1Z6n4xjvGmj40O Vj14vib/LA5ItQ/pWVl7U7Y8QoeMG45s FU8N4+TibInsJo8hnV3GM3t3B61QsziA gEvwvgD3gFRn4W1czvdxWQuQyyQVr8z4 Q3O78QHh5crzMgqlbnwrBr7q3a5+ltWb pAoEhCE7uccLmCIdde+bXg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20171124000000 20171110000000 43863 . YBZuPs+ERzW1BLvmihMVYebVLgSn/EeF L+R4S8RMgvYdPAHCqDjQy6vpT6JW9YUh r1JVbCw6gq2Wp6UqgHnYcTzDKCe8sodz yXUotZrUY8u0MEmcWMoeTR9goAq3EznW LPWvuE0jqB6qIV+1xFg21fkVaSR5g0Ex j/JT238NUIABu7xEoOkWIt1H15/CnRgk 9QSD575ERP3ksfNtoonO5B2cvgn2Rlxp PKZ12uP/bQC2Xxr3n6TVOkBiFs7RNm5H yxf3HXvb+K4JlYiC1VkrXlBX7dnbFUEk NLK/BVwFPZYmbJlTT+VrlvgbeWLkfEYK CKPhnMU5ZBRalkFQGqfAEQ==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20171120000000 20171129999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20171204000000 20171120000000 43863 . ijEdh2wgt4tTeSZRus0IBsndUKIa41u4 LmVEhXyi85hcsvkSt/FrTqZiZAm34f6A FcmtPt8Tk+mUq+c6PoBArVvt6Tr9FE4P haMCzKrNruthWe3+ch54l/yBbn93A+Xd hQB9KSn8IWMUMEjg+9Gnc08v2UavBo4L IKHRUyM0lSx0UX4gdrauW7XimWDwioSD 7VOFrcCmPTLs4JdEqd+LEOm7/qodYaX1 fWzIARcw+0fk8jHq0OqeJh7B4xiRSr+N 73bL9d39ljs6HK3A4gipoWhCab+F7ewI WZZ6BGHy1jIvm9+rPSKfFct8+0B2uApc xEyyz4gJZooFVgrP7uT4Lw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAbS4LSbmieIk96i3AK8IEZXlLRdM YLoEp4QSRMrdtJ0VcZKsjHWF6g9Q50T/ 1XKBxY/+6j71//cFRi1cViqoNsPd3LEb txWa3Jx/1HBINOkAPdHMBc3SQQ5qEIar PObq98RLnQ22ske1xZtbzNRZogPNi7zM HwQ3GYf0sUVCnYTmv8en5yYfSh8mY9Ez 8okorK7UN2RlHx9B8S5DRtBphO61mSI8 MXRj91aC3iRTbYZndTWorVkj4UvCBBr9 qh7oNpRbprIvAmRzRK6IaxpYDS6iGtSq 05WbGRtQRXkngOzFZO1BBDHgj9Agtx2S HBxAKuX4wF+w8I8xO7m5z0MOIF0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcgeCFZiwMD0S6TVgSK3Ob/MKFo+ dHYeNgoOjxH3JoxV09WShBXID/LwKs/e sYAIzTOBB8Fu9IDoVuQe5prOEcGZRp6u zF7JLnqrgOzoaSPRmKsvXh8DfipA56qE AqNwYBCqzlbGIGjHOQ8t+Xonp4fSBYbN MLmh7o8+u55k7PxemKTCiAnY7Bjx0g3m J1HuFvQXDDdvyletuoS3NKqrkjqdOHRB 3p3GKrKgw+zVb05UEyotCEKjqqi+BOmg cm8rmUIeaNQREjxrUyr8/Ry67h5NJLfq LAqAnSePf1PyhQcl8XjGHXN7w1tLLyJu Gy5B3zLSW1P6jIjon77PSn+cXi0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAfX4eSO5BEPXggvx4jL5HkEjbA+5 QD8Acnh4wOHNv/OJX4QhKnpoMDOrpp38 n0Rgcr8qZ0XUqiJWl8eEH//wrQDHoO5/ JHRmibVrcA6UqfhUa25D1BfeMtTgMCSS +W3/heB/YhyXOJtOdWDmJutDY1nfJUSK rF6XwBj4us7U5jvwXwq+l36AeI4q7I87 Std0GNgRdA6/cJtApPoGv/0oNE0iNTyu IBcAR+AeWu32+JavhTycmtMfZ+KOIShq uI9RHzChZ9yLikAT+gclJGLLlxIr6HDr SLChCG90/yvmc57dLn8qhRWRJtJeLT2V HSxeP7oREb6RSdkxET6OTf9GYHE=
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20171211000000 20171120000000 43306 . OFuHLGCCYvssVgObtX4ugAG68DDhCEIi nLTe5rZfhPc0cYoZbRkheyyeMeyV6xuV x4JVah4k5C6vabPK3sVbEWiWVgRhtPMD 8cZVBMb4EZHmqbXCRV8NYZQ62JNwhsBx pgFWLTd1Fg9kpxS2opDvj/86w6JuOX60 KJ0ASWJsqujbH+nPfxKZEsddImXfu98Q czav9cw3fN1PQqt/UBjrwglDPz3YpkFs ce21EwgAd46p4CboEl8LGzU4QWrz4ndR OdG1vdgFCLq1bhwOYXciXT1PNGPnkMUT 9mpAtNOgR/ufIT+XYNfM3C9MJI7ukmUx Wo5VkH0GN2Ogo72t3BkGYw==
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20171211000000 20171120000000 43863 . rVYGkFuBpbElsJzwG4y2LOZvmTPLFXV6 /3jh6G4b951ILKhLTc9kl/Ecc6X09Pow 0pZtMgvZlQt+zfgHRaYaZ+cG05mbpw5C ojOJRycXgxVrzhPI2KtDu12iD1Uov2P0 ytE5msd57PDq8uwdvtj3e/7Vfyx9qVr5 QEEXqtV0EBAMIrNLRpRPav3jViGodY8v kfDP0YbJh1fTz0LFFmzP8I1WlSi665Q4 HT1KO6TdTrlZl1fbMlbJmop+zcZ7wWft 8M+PolxhwPpT0LIctA93mrCr1t+jTx/Y 3TeA/vYb3KHgpypNYfPImbnrxSuruyJ/ 7+lwmLKbuxb15NHUrDaeHw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20171204000000 20171120000000 43863 . ZRzL9+U8+HkObgmmCNGJEhL4lwbgVJYV EY6qin9F2xvq68t4H57D2d8+7jjCmmnm +GhzrjckeHVOrRO9ujt18NTpfhR86n/D eNLK+NfsLCW2i7l8lAj6LJoYZnPcdSeD KWC1U6HjYrzsuQRSKDkPEHPARgYfceM4 WcHnb+QhTgEueSMkikEQI8w3wQByWIlD kAZxcE0wrzHxxnFyqav5UsmJX0Jscb+h kZSTCwK+YMcrAyo8/t3O+7wP6XRb+MTs LnmXUo/53FtLPjDxX+3p6e2aPvEuOgXi rySMuE3+o5SIWgP/cD6yrHJjmdTelWMe vjXQ0NXILFy/+2/iTi/DCg==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20171204000000 20171120000000 43863 . pbyOQnscKVBkn4Ik1VwX7tPt/1i0W9hO AOg/X9WHDdQLJBaaElCRc0njtT8HqBYe PJ1E6+tG0MLNbznGpjUaJ8FwInjbzqug 70WuDyZxS6GW5GPtP3PaK+cv+Kwc+sj6 OimX67TmCIrkA8aza51RfdfuSccY/MLx HwEgzfKBWOzbnECd92P86D901Fyr9xWj bNlQVj20SxvkTaQ8wgTnaslG5kSRJCbv 8gNG0WFYVVUoDU2oMS0xx/Rr+qWMf6+f ZXG0B/T0QJm/84ov0BYibVXwJTwLIJC7 8GB9RBdO2lzpiSvhl+PCt7cgpsQyoM9y jhV1iVeYccqf6+aQ0lEr/w==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20171204000000 20171120000000 43863 . Ge7dUr1EmWWJ9/rleiFkH1M+2GqwIufx y6OdMww2tvQV0R6lfcz8VWXz4xGfVG4L HJIdYVTnVLV+xnVafxGBJVfWcqfcvKHp 3qeskzgl118QPAgf9SY53U+bKwg32qWq GyGp5XK1LWnVAMAqilEbI+KnP/sMOB4T d0c6BEdpGi3hV+ZC/Mf/vGf0FSJjBOcS 5OsC/wl+1BnM7vmuMAaoPU4mOrF8f0mK Z3gjaZijU47TGfl3K/Yayr12nOtjs3hh 0f4er+3UhxglBYVCJzxaZj8GGT9oqSKy 9LCrK9ZqeBg9QgxB+57RBwEG/KlqdQe0 PWPHGjhzRJSMF+T6SMdxDw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20171204000000 20171120000000 43863 . ijEdh2wgt4tTeSZRus0IBsndUKIa41u4 LmVEhXyi85hcsvkSt/FrTqZiZAm34f6A FcmtPt8Tk+mUq+c6PoBArVvt6Tr9FE4P haMCzKrNruthWe3+ch54l/yBbn93A+Xd hQB9KSn8IWMUMEjg+9Gnc08v2UavBo4L IKHRUyM0lSx0UX4gdrauW7XimWDwioSD 7VOFrcCmPTLs4JdEqd+LEOm7/qodYaX1 fWzIARcw+0fk8jHq0OqeJh7B4xiRSr+N 73bL9d39ljs6HK3A4gipoWhCab+F7ewI WZZ6BGHy1jIvm9+rPSKfFct8+0B2uApc xEyyz4gJZooFVgrP7uT4Lw==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20171204000000 20171120000000 43863 . UByOkUebyZ68uVjCkAC/yAJSkAosnKMr hIsV428f844njgazLWSq6KjsfCo0WTLz peH9rwQGzure/8Dd2/vxpYjxvT/85jEC LuBAAInNYt7GIz+UrPDCpfe43pE0L0cK XStz/x0R9mNGH5/sq5MfQOxaVWWWZtuV 5S0MVpoNCnNkBlPcCrJT9tV3uL89lptT tiQ07PYCeEkB7nmC7E96jDpopMlo/PC5 zK2bXBh9LuG8UjlvNzSeelE72CkhId1+ 8FrXO/1lpCX4Cegp8gHtTiVEr6ZQMz+d dmLLl9MvPczXXAf4W1Rqa0BG94BU3ZwN ivElxk1UXlxV64WNAQSFsQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20171204000000 20171120000000 43863 . pbyOQnscKVBkn4Ik1VwX7tPt/1i0W9hO AOg/X9WHDdQLJBaaElCRc0njtT8HqBYe PJ1E6+tG0MLNbznGpjUaJ8FwInjbzqug 70WuDyZxS6GW5GPtP3PaK+cv+Kwc+sj6 OimX67TmCIrkA8aza51RfdfuSccY/MLx HwEgzfKBWOzbnECd92P86D901Fyr9xWj bNlQVj20SxvkTaQ8wgTnaslG5kSRJCbv 8gNG0WFYVVUoDU2oMS0xx/Rr+qWMf6+f ZXG0B/T0QJm/84ov0BYibVXwJTwLIJC7 8GB9RBdO2lzpiSvhl+PCt7cgpsQyoM9y jhV1iVeYccqf6+aQ0lEr/w==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20171204000000 20171120000000 43863 . Ge7dUr1EmWWJ9/rleiFkH1M+2GqwIufx y6OdMww2tvQV0R6lfcz8VWXz4xGfVG4L HJIdYVTnVLV+xnVafxGBJVfWcqfcvKHp 3qeskzgl118QPAgf9SY53U+bKwg32qWq GyGp5XK1LWnVAMAqilEbI+KnP/sMOB4T d0c6BEdpGi3hV+ZC/Mf/vGf0FSJjBOcS 5OsC/wl+1BnM7vmuMAaoPU4mOrF8f0mK Z3gjaZijU47TGfl3K/Yayr12nOtjs3hh 0f4er+3UhxglBYVCJzxaZj8GGT9oqSKy 9LCrK9ZqeBg9QgxB+57RBwEG/KlqdQe0 PWPHGjhzRJSMF+T6SMdxDw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20171204000000 20171120000000 43863 . SlxNxV5dq19/lFcTpRpqn6ux/KwAQV87 0YKUjQxS//BXgkd65+hGRfnBLEIrF3JA 0GGpVqzVuIlVvNIUTq82QitgYBqO283H EwJTvH0ObcKVTMD+snHcQs3IL6bbwRCD uJabvcIQh4Id1uT6S1kYjkGqii5HgRAN 34boOGku9thUBByXW2XX+daPTLYD2yK8 KPzgaaWssaqZ9w7Ef1jr1ijcPQNlAraO nS5CZVT4+jLFSOSPPKRkR1sGGUry9NAl GDoaWp8Fr3D6AI38NZ1vtMdE2b66yb4I gjy/3TrHfqOIeIoJ39EA8hCB+bHRURO2 bcsI/t7U/aYzdTlU4fguWw==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20171130000000 20171209999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20171214000000 20171130000000 43863 . UCxuHrfdTZiT4SdJ7i8hcVu7NVyI07CE BkA856ZsxXq0R/GM2MqJu+QugBJARBE8 QNues+v+FqWMfaMMQ8mSOq1QfqChCjx1 eeLahhBPEY+H+d3zE/IXzxHs2eJr9voB 8U5hSznwhJu2kLR3Q6M6SisKME1/IO5V lEV34XMScd6Ys5jVGVwu+6rZt3oSksVh t8g0k+MOVnPFQvA6lcd4sDY5rpMHqQ0h 8nd4OaidLaYM2cbGUtsjJTZdmLIj60Ah 22HIv69gxy7C8sg2/2nz8VHtklOpOLxL uxWWt+4gLR3oNsg+HR3ospSMBfidmu4X /K3aUK7e6fg/khI5kibXIg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAbS4LSbmieIk96i3AK8IEZXlLRdM YLoEp4QSRMrdtJ0VcZKsjHWF6g9Q50T/ 1XKBxY/+6j71//cFRi1cViqoNsPd3LEb txWa3Jx/1HBINOkAPdHMBc3SQQ5qEIar PObq98RLnQ22ske1xZtbzNRZogPNi7zM HwQ3GYf0sUVCnYTmv8en5yYfSh8mY9Ez 8okorK7UN2RlHx9B8S5DRtBphO61mSI8 MXRj91aC3iRTbYZndTWorVkj4UvCBBr9 qh7oNpRbprIvAmRzRK6IaxpYDS6iGtSq 05WbGRtQRXkngOzFZO1BBDHgj9Agtx2S HBxAKuX4wF+w8I8xO7m5z0MOIF0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcgeCFZiwMD0S6TVgSK3Ob/MKFo+ dHYeNgoOjxH3JoxV09WShBXID/LwKs/e sYAIzTOBB8Fu9IDoVuQe5prOEcGZRp6u zF7JLnqrgOzoaSPRmKsvXh8DfipA56qE AqNwYBCqzlbGIGjHOQ8t+Xonp4fSBYbN MLmh7o8+u55k7PxemKTCiAnY7Bjx0g3m J1HuFvQXDDdvyletuoS3NKqrkjqdOHRB 3p3GKrKgw+zVb05UEyotCEKjqqi+BOmg cm8rmUIeaNQREjxrUyr8/Ry67h5NJLfq LAqAnSePf1PyhQcl8XjGHXN7w1tLLyJu Gy5B3zLSW1P6jIjon77PSn+cXi0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAfX4eSO5BEPXggvx4jL5HkEjbA+5 QD8Acnh4wOHNv/OJX4QhKnpoMDOrpp38 n0Rgcr8qZ0XUqiJWl8eEH//wrQDHoO5/ JHRmibVrcA6UqfhUa25D1BfeMtTgMCSS +W3/heB/YhyXOJtOdWDmJutDY1nfJUSK rF6XwBj4us7U5jvwXwq+l36AeI4q7I87 Std0GNgRdA6/cJtApPoGv/0oNE0iNTyu IBcAR+AeWu32+JavhTycmtMfZ+KOIShq uI9RHzChZ9yLikAT+gclJGLLlxIr6HDr SLChCG90/yvmc57dLn8qhRWRJtJeLT2V HSxeP7oREb6RSdkxET6OTf9GYHE=
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20171221000000 20171130000000 43306 . KWzRB5lrZNkjIYKcZAWMvbQyadre36hz UhSr/lJAvRUvpWotDhgTzr0wezKHtgyH vh6zUhGn4bJVKWU13ikum9+X6IgjALy0 GU+teeNhq+6v3rL1fCUSYaG5uxsutGZH QQF76XcVXMwmE9zU8ksFP45SQnjbhJW0 j1UPrH8YnUzlX2ElvdcZvJmF61xYt5dt S1wPk0qBsTpKvh0tY0nOpY4bHVkm/uG9 notsyRbxzZHGb2hGjAFVL6fRDFGLltBN l5OpJ50ml/wVcTcSbXV+at1oxZsBCXhK Knchw8FBRBoIC+X88UXdoN410QE8qYXC u/aDb2lWOr7nJCTa6GPTGw==
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20171221000000 20171130000000 43863 . hmNpauPne5OdaR9AmO0F8RugnhM1zyEQ +Xd3rzcW0YmQtf/ZkDuJJExr+4zWePvo ABf85e9KoOjHKA3gIh1Cvu+KYIJWGHzi jBGA6QAI/xcrPlA/1HpI93PjJM4wvGrO Lu6sOMTGg9HTtXP6tlx7/ynl2Ap28W5Q b0MS2I4q5aklMC1JTPUAyRTO68WoqT1c n7RH1PPrq0f5UWOPzc8htCDf66UGhjDm EEM678tF9wNIKQxavny/hKHYk5ulj6Yj clbsATUlCV++V55eg+mpQAGE+LFVdwNU 8oEgLX+wCpSI1tgPZEi28qPIufKn16Y/ wK3UBWMjHEt8ZrTQGUVF6Q==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20171214000000 20171130000000 43863 . l0g5TXXkZS/8gRZTabMisId2gKjEOX+S Zf/rsZDISgFwgGHiXiHl2AF9Xt1oXNa8 eQ6jgfyrb3blorYxnKLpPHvQgGmlQYGj +DQvprLE+2WEfuqNUJJiyS6YQt1PUG9W 7Js6oupPzmpKsJFmQJCrc9gqu2FaTBau TK+8hBF92vJT71Lz+aSytByAilMYeOiZ kk7tJOaVKI7j2yl6D5ymzi234XHg/UxE pODae+XWbDp/7cQrSpDmAqrIf4BhhCZf jdNn5ZPrKWYqMRQhzU8fNNLcLFe6TsRP eVf1S4cZXJaje+qFHRwPjB2eqH9ZFnft ReTDezSHakHbdFOvPFKP4Q==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20171214000000 20171130000000 43863 . kaKieodcrDtLvvgfxLbZzx5heRAxrWIt 4DprkLP0l7nR2n5gPB4TX9toRqAART1X iWl06iDVXa9P8fqwCfpJa36mkI4WEecq 8AbRT6UxXpAVso4/uvR+UG8c79kueXkF 6TpZoaKs+4S1IwO9P2nefiyaQ+AJ9D7a KpBjcSfF+zKY3fIj/ck+fc3Owz2XNtJ5 qWRtppnRQ5t4gzYAIJeULC+622k8QSpL qb/1rhOCm5aGTxIOGVjvW8Zt5wGlJEd/ xvM6hW0NcnQU/4w8gOBJuraklWXTnG6q GKtdZtTJ4P6xTOiKG3kROrSIBwQ1iUR2 7qlmLabMHrTZC+cOcUXA+w==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20171214000000 20171130000000 43863 . XZROZxFmRY6FygyYribn7/xFRRke0M+s Q76DiSDdL6R1/NlActeocZ6RjG/l9icS Ca9GZqczS9qcjs7dhQtvyeK84cV31YlW Lv3LiQGmHP07+4Yv6Lq/BPUaRcbbQldw dgwKXDgC8P+z8PocjBA84dyIVV8q7yRm niusMJNDpA5r7rwHfM1JUzaj2e6tsRjX IkXzReSul7Z0DZ8yQuPL0vh16XigbkO3 ZANdKw2flCynvcI/8tV8Ni+lPIlH4+E5 1SMJ/485NKmjPDK4AsS4Ic3u6eonen6v I+jq3xii0cWjzM3IEma1oin8ZzL/NWXl hSodAhBe5eMgV5zx+SeQDA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20171214000000 20171130000000 43863 . UCxuHrfdTZiT4SdJ7i8hcVu7NVyI07CE BkA856ZsxXq0R/GM2MqJu+QugBJARBE8 QNues+v+FqWMfaMMQ8mSOq1QfqChCjx1 eeLahhBPEY+H+d3zE/IXzxHs2eJr9voB 8U5hSznwhJu2kLR3Q6M6SisKME1/IO5V lEV34XMScd6Ys5jVGVwu+6rZt3oSksVh t8g0k+MOVnPFQvA6lcd4sDY5rpMHqQ0h 8nd4OaidLaYM2cbGUtsjJTZdmLIj60Ah 22HIv69gxy7C8sg2/2nz8VHtklOpOLxL uxWWt+4gLR3oNsg+HR3ospSMBfidmu4X /K3aUK7e6fg/khI5kibXIg==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20171214000000 20171130000000 43863 . OmAfGDmBR47KWGoXaW9u+WIj3zqBg8ia ASd+SywAflqH2cvx9St9lBmN/g4hzof1 l/2O1mMDU/8XlAcZ3slpnTt5XE0NNnDr 5GQpPlVhrbvYnriSpl7LGamgf/i5BtJ9 eu5jP3wBb7Ra79EGT60QgcwbHPyFCUbh EUW31Ak2Kupai3LWc/3vXdgf2DWk5MXv C9g9QEpE/6Zcn2H9hxLgXghlEGighcZP a+sPcOacytQpSi/ggMX0ztLj20DM7Hes 3YUwG84SlcDhLpLN5M009hFlPu2+pVjC CmFywB4dtyUS62014UIR+PAByFcFEGOS MreqUAs/wj3RnpevarcaBg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20171214000000 20171130000000 43863 . kaKieodcrDtLvvgfxLbZzx5heRAxrWIt 4DprkLP0l7nR2n5gPB4TX9toRqAART1X iWl06iDVXa9P8fqwCfpJa36mkI4WEecq 8AbRT6UxXpAVso4/uvR+UG8c79kueXkF 6TpZoaKs+4S1IwO9P2nefiyaQ+AJ9D7a KpBjcSfF+zKY3fIj/ck+fc3Owz2XNtJ5 qWRtppnRQ5t4gzYAIJeULC+622k8QSpL qb/1rhOCm5aGTxIOGVjvW8Zt5wGlJEd/ xvM6hW0NcnQU/4w8gOBJuraklWXTnG6q GKtdZtTJ4P6xTOiKG3kROrSIBwQ1iUR2 7qlmLabMHrTZC+cOcUXA+w==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20171214000000 20171130000000 43863 . XZROZxFmRY6FygyYribn7/xFRRke0M+s Q76DiSDdL6R1/NlActeocZ6RjG/l9icS Ca9GZqczS9qcjs7dhQtvyeK84cV31YlW Lv3LiQGmHP07+4Yv6Lq/BPUaRcbbQldw dgwKXDgC8P+z8PocjBA84dyIVV8q7yRm niusMJNDpA5r7rwHfM1JUzaj2e6tsRjX IkXzReSul7Z0DZ8yQuPL0vh16XigbkO3 ZANdKw2flCynvcI/8tV8Ni+lPIlH4+E5 1SMJ/485NKmjPDK4AsS4Ic3u6eonen6v I+jq3xii0cWjzM3IEma1oin8ZzL/NWXl hSodAhBe5eMgV5zx+SeQDA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20171214000000 20171130000000 43863 . LwubT77ebb9kYgMvgQIIR7/V0t9IgY2y fEXhCbK2nQ/ugSInCMb2zpoF6t6JswR6 07lvjKGaG/B/V7p4cKfJXdW6O/+ke/ub YA+MNVJ8tg2/sC244ATvH+rk+K7qvFWs TuBL77AaRRKU1z8V3AQPbka7pV+2myTL NlIEgd0Dh2dr823rEqgqnKxpTpobJpPv Z2HWQvPT7n7MSDsKyzYNaVINxwYo84Nb 5vit+Nh5WU/g+1gmsVhxfNSO+XPSEQE7 7YGfc/IaOoqQzSKo3q7mcLrTMA5RbmAO DMoJl5xaHpxHz5kYZvb4XOlDC+gWChj3 t99u7aun2VHpcaUyqdfk3g==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20171210000000 20171219999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20171224000000 20171210000000 43863 . TWYLfFkfkhzVbdQwXVasQj0RUQZsDiMU HJyRzISnRIL+6hoSi6+imw8z2PcvSQne 6QKGrab1cEZgDFiyzOcjYO5I0FGfQta1 IKrITe5n+l04r3WlqZL3r4acXaX8Pb3R tC02hYb+oIjxgTmauL9qkNHiISNaSi6S wjVEaeXC4kOvSPNBKaB/aXRp/DKU1FVi R+l2fofpIai4HpVxOAbJ2+qzDfG0vfzQ S8+RMfJ3s9hyqwluc/6loC9eABjve6Su ukRgoRzw5CmAKw2trUNLlcnLhLFt4YUG Qhpgzo3/FsTJiT2Eu15eO+zp03RhTv1Q KpnVPjhOhXH8aiVGLzHF1w==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAbS4LSbmieIk96i3AK8IEZXlLRdM YLoEp4QSRMrdtJ0VcZKsjHWF6g9Q50T/ 1XKBxY/+6j71//cFRi1cViqoNsPd3LEb txWa3Jx/1HBINOkAPdHMBc3SQQ5qEIar PObq98RLnQ22ske1xZtbzNRZogPNi7zM HwQ3GYf0sUVCnYTmv8en5yYfSh8mY9Ez 8okorK7UN2RlHx9B8S5DRtBphO61mSI8 MXRj91aC3iRTbYZndTWorVkj4UvCBBr9 qh7oNpRbprIvAmRzRK6IaxpYDS6iGtSq 05WbGRtQRXkngOzFZO1BBDHgj9Agtx2S HBxAKuX4wF+w8I8xO7m5z0MOIF0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcgeCFZiwMD0S6TVgSK3Ob/MKFo+ dHYeNgoOjxH3JoxV09WShBXID/LwKs/e sYAIzTOBB8Fu9IDoVuQe5prOEcGZRp6u zF7JLnqrgOzoaSPRmKsvXh8DfipA56qE AqNwYBCqzlbGIGjHOQ8t+Xonp4fSBYbN MLmh7o8+u55k7PxemKTCiAnY7Bjx0g3m J1HuFvQXDDdvyletuoS3NKqrkjqdOHRB 3p3GKrKgw+zVb05UEyotCEKjqqi+BOmg cm8rmUIeaNQREjxrUyr8/Ry67h5NJLfq LAqAnSePf1PyhQcl8XjGHXN7w1tLLyJu Gy5B3zLSW1P6jIjon77PSn+cXi0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAfX4eSO5BEPXggvx4jL5HkEjbA+5 QD8Acnh4wOHNv/OJX4QhKnpoMDOrpp38 n0Rgcr8qZ0XUqiJWl8eEH//wrQDHoO5/ JHRmibVrcA6UqfhUa25D1BfeMtTgMCSS +W3/heB/YhyXOJtOdWDmJutDY1nfJUSK rF6XwBj4us7U5jvwXwq+l36AeI4q7I87 Std0GNgRdA6/cJtApPoGv/0oNE0iNTyu IBcAR+AeWu32+JavhTycmtMfZ+KOIShq uI9RHzChZ9yLikAT+gclJGLLlxIr6HDr SLChCG90/yvmc57dLn8qhRWRJtJeLT2V HSxeP7oREb6RSdkxET6OTf9GYHE=
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20171231000000 20171210000000 43306 . j7fn3Wmksge84eUfePg/6iOXBNnx04hD s996uSsqG5oqXC4VOtLR9mfPN99xHGfO swEhDgV3EMyutxBAnPqk+d3mHVQQDOld MQ4KASoLaGrzmovSRiw/ne4ozgDnfrMV JFozXV+P9HhQ+tRWF3x01vx3H8m+aCIK NaRUSNjs8bTJ8JvDEa4PxuD3uPgcJ7Qe UtMsQjaotPbenA1zTVx/E9LtI/wX1Tzs UOa89+dXWZDuny8mBKWD7KOIi63Upsnd zGbogel+QuLsF7E0AlinjTZD8FzaAujt hUUw66TGyEfciv888tyK/7nOhLmf+7qt X5LN+qfX5VBxt/wrN8SEjQ==
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20171231000000 20171210000000 43863 . sk7zKGpiNflz5vQUiBM/tGH4cCEwfv2E y/DGmjaa8DWHlui+GepsaRl1t7dDWBW7 JxqDm1cESd0uU3BOqEDv+zGBbCYuhM5p WTqb2Fm3r98uH8jhXpXkpj2xMZw9D3Fo fnsotjc3a7mMLPYf4G/WyCJDYXSFOI76 dsUXwNB0vbu0vpwbPzDLFxNbwIU2cAnW rFATn8yhUU4qZyuiZyfIJFMBExfoXSiy oHa6sDvngz8Ve83eIom+B6hN1IndD5i0 C+d0uZ+tZSu2lJA6yXXPiztu2w8RsJRE qpCvBHZEZeSVz59yfY9sM+5xYgC7O492 xdiHCDZy0S/7inugSZVq4A==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20171224000000 20171210000000 43863 . FcAn2lVVdJ0QH3oYR+3YS/ReGs6pEdJU Wzmb68N7OG7V4bHovlCBVY3bGaC4+a1k Ye5t4uImqSTAygc/Ih8Nh4zUP+Ypmg57 XkQPAizWW/pc3yMVAxnGiF++do7CTf9W L9ZKknNsfhKN4L7+49BdPk3zbi8taj8L IUl9Oxic2F5QoTxb9MJ6Aetb8HXFMd9Y cmGa1/SLwDu74F1vXXZ5Br73RzTh3J4m y30T1uWvcVf76fTgsDVnj93sc/pSXQVk MARPd6OpGp6x4wGjN9LPOTAG8uV/g51y wJCdOcXikuWxX59dXIp6fnY+5GLyPyRt wmb3v54kwnXUHe5IyIT73w==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20171224000000 20171210000000 43863 . JVTjFY4Dsa4jcl2lNy178dO/4XuXzM12 do4ddbz80Zri18P6JW4ro/fUAN142jtl ybwrt3MRUpZQUXKhQijQpmiEYIfHPrc8 Gl4gitSnEPFjemyN6tOWJH8tzOPhkmm0 IqpdtLKG1MRuDsE50MAoVxA/C82ZzHza JExrCZxnRYMVkZSd19DacE2yHMDSXSMC OhPSEft1Jc5Sk5bUk9Jh27zQ59ZoccUq fE2nCeApt4d9WSRMAPRUlvZC8NHYZ8Sd WY/7/eX5XB2pw0xExtTwTQFPyPXxv1kt L5F+vrdwPrVgHXI3XD5lDRY8J80uyfo2 YwX3mGxAENwXPu0B+/GuAw==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20171224000000 20171210000000 43863 . JI/lzbKGeJ2b25hqnJlrJ6SYZhkIiti4 pJs9F1dTa+xuYGv/rj7E6/g3H/sRxfS1 OKwOUfB5jTuhrbEmjffqHB7f+5+kNleE EJiDwwKW5KROtPhmwcYmTFnH+sjyotKB Wh3MHqbSTdKjtH1oZ0y1c2c4se2R+nE1 DKhjCPr2+cznWgraZld68aQ/anGktEr/ ib9zw0gH9cy88WMqV6g/vrTQOLM1Zl9F PM/zvuveDqrVbATRSEeAieK5tVlRXc+0 3ReWMme2kYQrnxik8UZA8VXZpzwakcFi HJagYURn/ZZtbXgyDkrvJN3S+EXrZ4TI C+jX3OlwAIOvMZUr0uIWug==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20171224000000 20171210000000 43863 . TWYLfFkfkhzVbdQwXVasQj0RUQZsDiMU HJyRzISnRIL+6hoSi6+imw8z2PcvSQne 6QKGrab1cEZgDFiyzOcjYO5I0FGfQta1 IKrITe5n+l04r3WlqZL3r4acXaX8Pb3R tC02hYb+oIjxgTmauL9qkNHiISNaSi6S wjVEaeXC4kOvSPNBKaB/aXRp/DKU1FVi R+l2fofpIai4HpVxOAbJ2+qzDfG0vfzQ S8+RMfJ3s9hyqwluc/6loC9eABjve6Su ukRgoRzw5CmAKw2trUNLlcnLhLFt4YUG Qhpgzo3/FsTJiT2Eu15eO+zp03RhTv1Q KpnVPjhOhXH8aiVGLzHF1w==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20171224000000 20171210000000 43863 . sS3OI7gVoxtgc15H83an5b1KRhZyqF7U f2uAnSdy+dVqOpGbcnI98dVJ7n0SGZR9 snpfl0mMTNd5aVM57FEigkcOVjN6h2+b JgXmIsxPCIoKLJ9fCBz8GC/6UvBaDt9K h66FrkWz7IXBXBqkGyAjoeN4hyelisun wAgo1CfpAHcwWjc8IknfM6T606RMD+03 QuPqlzdXs4L2Kp5ozyuQH5MiP0to7Cvd bBj35zvGrNLs5IAK74rQJaLr7N5WVvpQ IXFNslDboc/PY/0UotPq34oSgfOUdy5V SesteyEziztLJgWpb/4F7FJrtDaRcTzM +0xpQKrk0toob/02m5yccg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20171224000000 20171210000000 43863 . JVTjFY4Dsa4jcl2lNy178dO/4XuXzM12 do4ddbz80Zri18P6JW4ro/fUAN142jtl ybwrt3MRUpZQUXKhQijQpmiEYIfHPrc8 Gl4gitSnEPFjemyN6tOWJH8tzOPhkmm0 IqpdtLKG1MRuDsE50MAoVxA/C82ZzHza JExrCZxnRYMVkZSd19DacE2yHMDSXSMC OhPSEft1Jc5Sk5bUk9Jh27zQ59ZoccUq fE2nCeApt4d9WSRMAPRUlvZC8NHYZ8Sd WY/7/eX5XB2pw0xExtTwTQFPyPXxv1kt L5F+vrdwPrVgHXI3XD5lDRY8J80uyfo2 YwX3mGxAENwXPu0B+/GuAw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20171224000000 20171210000000 43863 . JI/lzbKGeJ2b25hqnJlrJ6SYZhkIiti4 pJs9F1dTa+xuYGv/rj7E6/g3H/sRxfS1 OKwOUfB5jTuhrbEmjffqHB7f+5+kNleE EJiDwwKW5KROtPhmwcYmTFnH+sjyotKB Wh3MHqbSTdKjtH1oZ0y1c2c4se2R+nE1 DKhjCPr2+cznWgraZld68aQ/anGktEr/ ib9zw0gH9cy88WMqV6g/vrTQOLM1Zl9F PM/zvuveDqrVbATRSEeAieK5tVlRXc+0 3ReWMme2kYQrnxik8UZA8VXZpzwakcFi HJagYURn/ZZtbXgyDkrvJN3S+EXrZ4TI C+jX3OlwAIOvMZUr0uIWug==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20171224000000 20171210000000 43863 . bI93JmKmTuyDeGyek/OL4FsoK0wcr2DT enIf9HiEuLhsffQQAyfa2l4rEstTgBAP w75VDITGASJpar4qjtmCkYnXvljA+P5y MvgyOH8QmDIEc2795D4RkgAdvi5f7U4d tVMPqaFpSe36w1g/89ubn11Emn2Izq3w QnIkCpfAj6E9Hsq7SsXogUbt5vNc15aV jnG98LgrRaUtjCWWLQ72H4SXJtsM3T1H 8SvHYfNFCY49fmbhFubWa6Ai4SJzUZ0A p0vK/ecuSuUxZofYASkNRiu4pZqzKrY7 L9DMI53EE6/fPqPBNb01XakvNff9mn5Q tGL0o3GQN0xmxMwu2rXUJQ==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20171220000000 99999999999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20180103000000 20171220000000 43863 . oUqoqEdKMlNikXS+IfDF99twoBLkQeMp FNH8xdI7bN+CJ3asb+Zm/VvPNXjhu3fd +4HMwRJVT1Ar9IWI5CYKvhMPuTuiCgXO Nx7tTIYD0xGtIPeRwRBI1+M5v7iOdtwD CyCb9IUtAQu6Jcg4hC8LUFTpo53PWBD5 V7b3EC18aKOqpn5C6YwoXSal1LlNgTcr wIYs1OjAtDWgKtFbK/YVOOavgfEYs6eg Fwqtn1xNAhpSDc5CnMUdl2gg/ejUvUvs 28/e2Wcp4xg5B6pB0mbHs1RjBYES2Vo/ OMyKqY1/1GJhqLm5Gal7h8nW6LHJSkjH K6YenULi+qMDAaC4kCFtqw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAbS4LSbmieIk96i3AK8IEZXlLRdM YLoEp4QSRMrdtJ0VcZKsjHWF6g9Q50T/ 1XKBxY/+6j71//cFRi1cViqoNsPd3LEb txWa3Jx/1HBINOkAPdHMBc3SQQ5qEIar PObq98RLnQ22ske1xZtbzNRZogPNi7zM HwQ3GYf0sUVCnYTmv8en5yYfSh8mY9Ez 8okorK7UN2RlHx9B8S5DRtBphO61mSI8 MXRj91aC3iRTbYZndTWorVkj4UvCBBr9 qh7oNpRbprIvAmRzRK6IaxpYDS6iGtSq 05WbGRtQRXkngOzFZO1BBDHgj9Agtx2S HBxAKuX4wF+w8I8xO7m5z0MOIF0=
+. 1814400 IN DNSKEY 256 3 8 AwEAAbgig4A5ky59AgWQLv02ZWvrP/PE +7D6PaQuOgbdmhdFqTQerNTEu1AztTaC 0QpMsG9HuhHi5rcOYGHHyxDagghZa4mQ Z08XWA+6Q2xBo5CMRh6+XTACY/N8ZoGU Si53XiVVh12t+BfbREXnT5zH45BBhPe1 dXYn8tolXXfK8oFhl8K0dvU4xhmOC1xD bDCeX1vyP0WyKtQzBU3tLHRIQQvss8vb iY+y3yTdJu062inDpGC+grJcY2nd4x16 ydcCUVRg8teLO0uuvfa0sdKLaoKJWWy7 CGWZX7lA4Nnb2CKWm0/ZySKIeSqOYW1B hr5MjdlzbyotJPhbxvBUUr3T+Kk=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcgeCFZiwMD0S6TVgSK3Ob/MKFo+ dHYeNgoOjxH3JoxV09WShBXID/LwKs/e sYAIzTOBB8Fu9IDoVuQe5prOEcGZRp6u zF7JLnqrgOzoaSPRmKsvXh8DfipA56qE AqNwYBCqzlbGIGjHOQ8t+Xonp4fSBYbN MLmh7o8+u55k7PxemKTCiAnY7Bjx0g3m J1HuFvQXDDdvyletuoS3NKqrkjqdOHRB 3p3GKrKgw+zVb05UEyotCEKjqqi+BOmg cm8rmUIeaNQREjxrUyr8/Ry67h5NJLfq LAqAnSePf1PyhQcl8XjGHXN7w1tLLyJu Gy5B3zLSW1P6jIjon77PSn+cXi0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAfX4eSO5BEPXggvx4jL5HkEjbA+5 QD8Acnh4wOHNv/OJX4QhKnpoMDOrpp38 n0Rgcr8qZ0XUqiJWl8eEH//wrQDHoO5/ JHRmibVrcA6UqfhUa25D1BfeMtTgMCSS +W3/heB/YhyXOJtOdWDmJutDY1nfJUSK rF6XwBj4us7U5jvwXwq+l36AeI4q7I87 Std0GNgRdA6/cJtApPoGv/0oNE0iNTyu IBcAR+AeWu32+JavhTycmtMfZ+KOIShq uI9RHzChZ9yLikAT+gclJGLLlxIr6HDr SLChCG90/yvmc57dLn8qhRWRJtJeLT2V HSxeP7oREb6RSdkxET6OTf9GYHE=
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20180110000000 20171220000000 43306 . iBsFCS0+aXyL+rLqQ45jBe1l6LSXyzFF w4kkB3opgU5o7TdaSnclpvGlabO2W1fR v9WrVDFPFYfWsMBH6TebraLm2do5HkWi bEaA8sZVKCv5Cew0FMRKtmcCYwFC5rRi MBY1ftWm1smyMEo4cU3ZvdH2Nd134Vmj xjN6LXJNJ/cx5TGp+AI0OSkdxIkVcPHr 6oQXM23WyTYp4QALnfEoh33jVlgiG9YQ 3OJ4IiKOVaeYZd1HyLaUVrreViuBCyWs S/tIScwBxKo+XImuqnNMRbun/jmkoTS/ 6BqUNxzjSq+jS7ayzXgnHsTKlXFPSDg4 bBelCv1A+DA2NG9NE4624g==
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20180110000000 20171220000000 43863 . HOvNMA8EiqTdQR3kuRm1Djf0hyphC5WM DffZbEQ0S3/PiXCUYqt/8WhGgs9NDj7l engBOouhNrmahtIM6W6Q4egeZjRX6hrf +Oe9hXa8EyStHZEdM3Bt9eAiptxliBfz OO5N8/+6vT4lSgn02zigc2S7UWVsezM6 kwhhm5m2IUb3qXpAqJ8rwrwhVKdoGHOr QStBrrLgOHRAaZf68GjI+E2KRneu90M1 mofkjA29iZdXK8+XlEhxDnLVfR3zZflL /IjE6kCGOaNurrRhA0G3+g7wY91F+guT FsTqNKApCEvEI5O8N3XhxfJmKlWZ8UKf TTnrfxghMi11IxM3MRNsxw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20180103000000 20171220000000 43863 . rlRwW56KLEwKjMVwq7vWtEHH8iUe0ME3 NRntwA96dWMXtYNp9ggPp+XYmQ8/fl/5 RwYtdcj649KjxdpauOiyCgBLpdu4dQnT JjfsHPRRMyTa3JLTxmQcnKKL84gVSI9P 4YRoUNiD1GCx0PpTsImF794Eg4k6qWjW +TPoraR41qv1+GywDLc6AZvkyMvm73Ru Fvj0TxI8KZuHlmwGL0gBPFl1Eqm0c/Pr n0PuTJXaNkDMBU/Bqm4K1uoWsZdECyT8 hnbOLMYIH8CENHn0h9miCAISdj5j+2mc bruRI9ZUknLF2fTev058NvHCYUOOTMmu NggrZOoVAMhUpyWUf3eooA==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20180103000000 20171220000000 43863 . qqJJVS9HmQPp5iPEUanAyVRFVJo70IEH zXOyegIPrVV4xAknPZezkwlDTxPJKEY/ 2Ob2sbvGmMiXvZn7vER5MPPOcb6YlT3E x9ygW8pa/5dx/gtemYmY4yhW4UhST4yB j500JpBItlMlbIP87ElECY8KyvekZ1RC PQfacGqJvZzG7SoHAOA+jphNgan0Qir8 Cx6iUsc2MEw5UMJmTCtx8fpweVmxHObT 7EaSleGQabSiV153GzRcTMA1Ye1ETUE5 YqIy89lKBDJfAJZ2m1AMYthLQwGOTtSI xyxA+qBgZfxC8foBVXgLUGB03X554V7K 32v2myX5HFSCNMhkXnQdSg==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20180103000000 20171220000000 43863 . AXlfge8y4s4b0PS9u8ZhJsVaF52ihXKa SO+HR5yWAfzskmNTglPcv2R9UEkiXxuP EVwGz9vmWPo1STlBGw6vJ03TtGOR7BQW dn/20j4cYvtIM5RjosZ8u58OxKDsPy9e uLI+nlqx2gq/DUZ+pxySgV836As1GBhB c4QxQY3oCej52qDnWLCDfs+5j2JXx8q0 MiAH8m3H91tSJ3dWG198HmfpLTemBt41 WnYxBO0AS2Z9gf+TvYXulcbJMNwqXIzB 85NGzgq+fL1u11MJfjIQVW9F5bK7KTYk 8X+rT7b/DzIMSRupMJEYLCT8ECvlqOBM PQW8Nv+YSbLrKPBIJ2Vivw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20180103000000 20171220000000 43863 . oUqoqEdKMlNikXS+IfDF99twoBLkQeMp FNH8xdI7bN+CJ3asb+Zm/VvPNXjhu3fd +4HMwRJVT1Ar9IWI5CYKvhMPuTuiCgXO Nx7tTIYD0xGtIPeRwRBI1+M5v7iOdtwD CyCb9IUtAQu6Jcg4hC8LUFTpo53PWBD5 V7b3EC18aKOqpn5C6YwoXSal1LlNgTcr wIYs1OjAtDWgKtFbK/YVOOavgfEYs6eg Fwqtn1xNAhpSDc5CnMUdl2gg/ejUvUvs 28/e2Wcp4xg5B6pB0mbHs1RjBYES2Vo/ OMyKqY1/1GJhqLm5Gal7h8nW6LHJSkjH K6YenULi+qMDAaC4kCFtqw==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20180103000000 20171220000000 43863 . jlED8vYS7eDm/0s4ZrlCuVJfRVSfQKxY 3STI2gq0PptQx+M6ovJf1oxjw6rNLF7s 8DK8WnznqxEocXFaJaR6jgUggZ8Dkgj7 DHNfshwISf5P93qDkPCOLqQ62vHTBmNk Pww3+aV2atnKhNAoEe8OphpGGpFeE0W+ gRf1+GAp+1Tid7E8SBxduIzrxEtKKvJM l/Ow0rLal5MKrnejV45hgAsVF0r0ZF0B 2G7Ud2aK47ZGcIDybRUD/rUxYiPDjSwe PgLU5M9+4VDSCdW1z/sf2fgQP7kt8tzC dhxRu9wLlM/YFSJb25qBhDI267qEh9cE rA1X0mbnTkueDGq9Wty4MA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20180103000000 20171220000000 43863 . qqJJVS9HmQPp5iPEUanAyVRFVJo70IEH zXOyegIPrVV4xAknPZezkwlDTxPJKEY/ 2Ob2sbvGmMiXvZn7vER5MPPOcb6YlT3E x9ygW8pa/5dx/gtemYmY4yhW4UhST4yB j500JpBItlMlbIP87ElECY8KyvekZ1RC PQfacGqJvZzG7SoHAOA+jphNgan0Qir8 Cx6iUsc2MEw5UMJmTCtx8fpweVmxHObT 7EaSleGQabSiV153GzRcTMA1Ye1ETUE5 YqIy89lKBDJfAJZ2m1AMYthLQwGOTtSI xyxA+qBgZfxC8foBVXgLUGB03X554V7K 32v2myX5HFSCNMhkXnQdSg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20180103000000 20171220000000 43863 . AXlfge8y4s4b0PS9u8ZhJsVaF52ihXKa SO+HR5yWAfzskmNTglPcv2R9UEkiXxuP EVwGz9vmWPo1STlBGw6vJ03TtGOR7BQW dn/20j4cYvtIM5RjosZ8u58OxKDsPy9e uLI+nlqx2gq/DUZ+pxySgV836As1GBhB c4QxQY3oCej52qDnWLCDfs+5j2JXx8q0 MiAH8m3H91tSJ3dWG198HmfpLTemBt41 WnYxBO0AS2Z9gf+TvYXulcbJMNwqXIzB 85NGzgq+fL1u11MJfjIQVW9F5bK7KTYk 8X+rT7b/DzIMSRupMJEYLCT8ECvlqOBM PQW8Nv+YSbLrKPBIJ2Vivw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20180103000000 20171220000000 43863 . s8AQUwbk1av7JPMS3E1/K6Ew2Pv01lYo JkgknqA4+i5LK4Jrqj+TgDXt4er5sFfK bD9FjDRVIXQJkpLtb48YGsIGqZK0ePSd hS/+eyfjg7W0XPDBsLjuNmJBZ4/Gtih5 I8+pZs9o1UwwrLTX7LrWDQnJAsLY/kyP aZYmOrGgki9LNw6m/itOn1FcACRI3Fmz zGeW0MXwFKA+SE3yAMcoAjhGdYWaWMR3 jZAWhLQRHDvZXRUS9XjLsDWfMSN1pEJx UrQY7+Q/B3aEf9kNw9rAgMkKST3GeNEn ElUZ/gIr3P5mCVqHr8Bi2L/Mts/vo+2N VQx7KoJL7pf9jjSokV9NuA==
+ENTRY_END
+RANGE_END
+
+
+; 2017-07-01T00:00:00
+STEP 20170701000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170701000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170701000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-02T00:00:00
+STEP 20170702000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170702000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170702000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-03T00:00:00
+STEP 20170703000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170703000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170703000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-04T00:00:00
+STEP 20170704000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170704000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170704000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-05T00:00:00
+STEP 20170705000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170705000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170705000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-06T00:00:00
+STEP 20170706000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170706000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170706000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-07T00:00:00
+STEP 20170707000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170707000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170707000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-08T00:00:00
+STEP 20170708000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170708000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170708000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-09T00:00:00
+STEP 20170709000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170709000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170709000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-10T00:00:00
+STEP 20170710000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170710000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170710000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-11T00:00:00
+STEP 20170711000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170711000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170711000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-12T00:00:00
+STEP 20170712000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170712000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170712000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-13T00:00:00
+STEP 20170713000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170713000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170713000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-14T00:00:00
+STEP 20170714000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170714000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170714000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-15T00:00:00
+STEP 20170715000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170715000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170715000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-16T00:00:00
+STEP 20170716000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170716000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170716000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-17T00:00:00
+STEP 20170717000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170717000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170717000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-18T00:00:00
+STEP 20170718000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170718000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170718000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-19T00:00:00
+STEP 20170719000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170719000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170719000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-20T00:00:00
+STEP 20170720000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170720000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170720000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-21T00:00:00
+STEP 20170721000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170721000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170721000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-22T00:00:00
+STEP 20170722000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170722000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170722000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-23T00:00:00
+STEP 20170723000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170723000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170723000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-24T00:00:00
+STEP 20170724000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170724000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170724000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-25T00:00:00
+STEP 20170725000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170725000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170725000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-26T00:00:00
+STEP 20170726000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170726000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170726000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-27T00:00:00
+STEP 20170727000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170727000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170727000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-28T00:00:00
+STEP 20170728000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170728000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170728000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-29T00:00:00
+STEP 20170729000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170729000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170729000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-30T00:00:00
+STEP 20170730000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170730000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170730000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-31T00:00:00
+STEP 20170731000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170731000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170731000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-01T00:00:00
+STEP 20170801000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170801000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170801000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-02T00:00:00
+STEP 20170802000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170802000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170802000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-03T00:00:00
+STEP 20170803000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170803000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170803000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-04T00:00:00
+STEP 20170804000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170804000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170804000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-05T00:00:00
+STEP 20170805000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170805000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170805000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-06T00:00:00
+STEP 20170806000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170806000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170806000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-07T00:00:00
+STEP 20170807000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170807000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170807000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-08T00:00:00
+STEP 20170808000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170808000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170808000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-09T00:00:00
+STEP 20170809000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170809000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170809000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-10T00:00:00
+STEP 20170810000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170810000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170810000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-11T00:00:00
+STEP 20170811000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170811000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170811000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-12T00:00:00
+STEP 20170812000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170812000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170812000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-13T00:00:00
+STEP 20170813000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170813000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170813000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-14T00:00:00
+STEP 20170814000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170814000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170814000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-15T00:00:00
+STEP 20170815000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170815000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170815000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-16T00:00:00
+STEP 20170816000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170816000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170816000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-17T00:00:00
+STEP 20170817000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170817000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170817000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-18T00:00:00
+STEP 20170818000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170818000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170818000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-19T00:00:00
+STEP 20170819000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170819000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170819000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-20T00:00:00
+STEP 20170820000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170820000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170820000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-21T00:00:00
+STEP 20170821000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170821000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170821000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-22T00:00:00
+STEP 20170822000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170822000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170822000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-23T00:00:00
+STEP 20170823000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170823000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170823000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-24T00:00:00
+STEP 20170824000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170824000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170824000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-25T00:00:00
+STEP 20170825000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170825000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170825000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-26T00:00:00
+STEP 20170826000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170826000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170826000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-27T00:00:00
+STEP 20170827000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170827000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170827000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-28T00:00:00
+STEP 20170828000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170828000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170828000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-29T00:00:00
+STEP 20170829000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170829000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170829000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-30T00:00:00
+STEP 20170830000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170830000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170830000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-08-31T00:00:00
+STEP 20170831000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170831000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170831000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-01T00:00:00
+STEP 20170901000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170901000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170901000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-02T00:00:00
+STEP 20170902000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170902000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170902000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-03T00:00:00
+STEP 20170903000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170903000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170903000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-04T00:00:00
+STEP 20170904000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170904000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170904000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-05T00:00:00
+STEP 20170905000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170905000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170905000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-06T00:00:00
+STEP 20170906000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170906000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170906000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-07T00:00:00
+STEP 20170907000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170907000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170907000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-08T00:00:00
+STEP 20170908000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170908000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170908000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-09T00:00:00
+STEP 20170909000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170909000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170909000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-10T00:00:00
+STEP 20170910000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170910000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170910000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-11T00:00:00
+STEP 20170911000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170911000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170911000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-12T00:00:00
+STEP 20170912000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170912000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170912000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-13T00:00:00
+STEP 20170913000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170913000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170913000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-14T00:00:00
+STEP 20170914000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170914000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170914000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-15T00:00:00
+STEP 20170915000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170915000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170915000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-16T00:00:00
+STEP 20170916000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170916000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170916000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-17T00:00:00
+STEP 20170917000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170917000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170917000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-18T00:00:00
+STEP 20170918000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170918000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170918000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-19T00:00:00
+STEP 20170919000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170919000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170919000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-20T00:00:00
+STEP 20170920000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170920000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170920000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-21T00:00:00
+STEP 20170921000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170921000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170921000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-22T00:00:00
+STEP 20170922000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170922000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170922000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-23T00:00:00
+STEP 20170923000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170923000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170923000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-24T00:00:00
+STEP 20170924000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170924000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170924000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-25T00:00:00
+STEP 20170925000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170925000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170925000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-26T00:00:00
+STEP 20170926000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170926000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170926000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-27T00:00:00
+STEP 20170927000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170927000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170927000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-28T00:00:00
+STEP 20170928000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170928000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170928000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-29T00:00:00
+STEP 20170929000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170929000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170929000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-09-30T00:00:00
+STEP 20170930000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170930000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170930000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-01T00:00:00
+STEP 20171001000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171001000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171001000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-02T00:00:00
+STEP 20171002000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171002000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171002000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-03T00:00:00
+STEP 20171003000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171003000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171003000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-04T00:00:00
+STEP 20171004000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171004000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171004000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-05T00:00:00
+STEP 20171005000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171005000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171005000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-06T00:00:00
+STEP 20171006000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171006000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171006000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-07T00:00:00
+STEP 20171007000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171007000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171007000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-08T00:00:00
+STEP 20171008000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171008000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171008000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-09T00:00:00
+STEP 20171009000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171009000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171009000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-10T00:00:00
+STEP 20171010000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171010000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171010000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-11T00:00:00
+STEP 20171011000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171011000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171011000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-12T00:00:00
+STEP 20171012000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171012000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171012000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-13T00:00:00
+STEP 20171013000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171013000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171013000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-14T00:00:00
+STEP 20171014000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171014000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171014000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-15T00:00:00
+STEP 20171015000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171015000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171015000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-16T00:00:00
+STEP 20171016000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171016000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171016000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-17T00:00:00
+STEP 20171017000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171017000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171017000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-18T00:00:00
+STEP 20171018000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171018000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171018000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-19T00:00:00
+STEP 20171019000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171019000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171019000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-20T00:00:00
+STEP 20171020000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171020000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171020000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-21T00:00:00
+STEP 20171021000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171021000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171021000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-22T00:00:00
+STEP 20171022000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171022000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171022000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-23T00:00:00
+STEP 20171023000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171023000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171023000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-24T00:00:00
+STEP 20171024000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171024000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171024000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-25T00:00:00
+STEP 20171025000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171025000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171025000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-26T00:00:00
+STEP 20171026000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171026000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171026000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-27T00:00:00
+STEP 20171027000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171027000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171027000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-28T00:00:00
+STEP 20171028000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171028000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171028000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-29T00:00:00
+STEP 20171029000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171029000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171029000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-30T00:00:00
+STEP 20171030000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171030000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171030000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-10-31T00:00:00
+STEP 20171031000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171031000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171031000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-01T00:00:00
+STEP 20171101000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171101000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171101000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-02T00:00:00
+STEP 20171102000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171102000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171102000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-03T00:00:00
+STEP 20171103000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171103000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171103000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-04T00:00:00
+STEP 20171104000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171104000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171104000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-05T00:00:00
+STEP 20171105000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171105000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171105000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-06T00:00:00
+STEP 20171106000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171106000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171106000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-07T00:00:00
+STEP 20171107000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171107000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171107000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-08T00:00:00
+STEP 20171108000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171108000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171108000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-09T00:00:00
+STEP 20171109000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171109000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171109000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-10T00:00:00
+STEP 20171110000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171110000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171110000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-11T00:00:00
+STEP 20171111000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171111000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171111000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-12T00:00:00
+STEP 20171112000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171112000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171112000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-13T00:00:00
+STEP 20171113000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171113000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171113000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-14T00:00:00
+STEP 20171114000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171114000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171114000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-15T00:00:00
+STEP 20171115000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171115000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171115000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-16T00:00:00
+STEP 20171116000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171116000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171116000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-17T00:00:00
+STEP 20171117000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171117000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171117000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-18T00:00:00
+STEP 20171118000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171118000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171118000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-19T00:00:00
+STEP 20171119000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171119000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171119000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-20T00:00:00
+STEP 20171120000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171120000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171120000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-21T00:00:00
+STEP 20171121000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171121000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171121000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-22T00:00:00
+STEP 20171122000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171122000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171122000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-23T00:00:00
+STEP 20171123000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171123000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171123000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-24T00:00:00
+STEP 20171124000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171124000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171124000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-25T00:00:00
+STEP 20171125000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171125000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171125000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-26T00:00:00
+STEP 20171126000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171126000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171126000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-27T00:00:00
+STEP 20171127000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171127000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171127000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-28T00:00:00
+STEP 20171128000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171128000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171128000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-29T00:00:00
+STEP 20171129000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171129000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171129000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-11-30T00:00:00
+STEP 20171130000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171130000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171130000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-01T00:00:00
+STEP 20171201000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171201000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171201000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-02T00:00:00
+STEP 20171202000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171202000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171202000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-03T00:00:00
+STEP 20171203000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171203000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171203000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-04T00:00:00
+STEP 20171204000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171204000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171204000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-05T00:00:00
+STEP 20171205000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171205000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171205000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-06T00:00:00
+STEP 20171206000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171206000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171206000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-07T00:00:00
+STEP 20171207000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171207000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171207000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-08T00:00:00
+STEP 20171208000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171208000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171208000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-09T00:00:00
+STEP 20171209000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171209000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171209000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-10T00:00:00
+STEP 20171210000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171210000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171210000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-11T00:00:00
+STEP 20171211000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171211000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171211000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-12T00:00:00
+STEP 20171212000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171212000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171212000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-13T00:00:00
+STEP 20171213000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171213000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171213000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-14T00:00:00
+STEP 20171214000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171214000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171214000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-15T00:00:00
+STEP 20171215000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171215000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171215000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-16T00:00:00
+STEP 20171216000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171216000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171216000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-17T00:00:00
+STEP 20171217000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171217000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171217000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-18T00:00:00
+STEP 20171218000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171218000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171218000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-19T00:00:00
+STEP 20171219000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171219000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171219000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-20T00:00:00
+STEP 20171220000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171220000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171220000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-21T00:00:00
+STEP 20171221000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171221000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171221000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-22T00:00:00
+STEP 20171222000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171222000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171222000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-23T00:00:00
+STEP 20171223000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171223000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171223000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-24T00:00:00
+STEP 20171224000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171224000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171224000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-25T00:00:00
+STEP 20171225000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171225000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171225000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-26T00:00:00
+STEP 20171226000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171226000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171226000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-27T00:00:00
+STEP 20171227000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171227000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171227000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-28T00:00:00
+STEP 20171228000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171228000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171228000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-29T00:00:00
+STEP 20171229000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171229000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171229000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-30T00:00:00
+STEP 20171230000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171230000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171230000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-12-31T00:00:00
+STEP 20171231000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20171231000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20171231000099 TIME_PASSES ELAPSE 86400
+
+
+
+SCENARIO_END
+
diff --git a/modules/ta_update/ta_update.test.integr/rfc5011/README b/modules/ta_update/ta_update.test.integr/rfc5011/README
new file mode 100644
index 0000000..9b71987
--- /dev/null
+++ b/modules/ta_update/ta_update.test.integr/rfc5011/README
@@ -0,0 +1,13 @@
+Start with `genkeyszones.sh` and generate DNSSEC keys + signed versions of `unsigned_*.db`.
+Then use `dns2rpl.py` to run Knot DNS server with signed zone
+and to generate RPL file from server's answers.
+
+Generate RFC5011 test:
+`dns2rpl.py`.
+`./genkeyszones.sh`
+
+Generate unmanaged keys tests:
+`./genkeyszones.sh <--unmanaged_key-presens|--unmanagedkey-missing|--unmanagedkey-revoke>`
+`VARIANT="unmanaged_key" ./dns2rpl.py`
+
+See comments in script headers to further details.
diff --git a/modules/ta_update/ta_update.test.integr/rfc5011/dns2rpl.py b/modules/ta_update/ta_update.test.integr/rfc5011/dns2rpl.py
new file mode 100755
index 0000000..317d671
--- /dev/null
+++ b/modules/ta_update/ta_update.test.integr/rfc5011/dns2rpl.py
@@ -0,0 +1,222 @@
+#!/usr/bin/python3
+"""
+Generate RFC 5011 test simulating successful KSK roll-over in 2017.
+
+Dependencies: 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 successful 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 --git a/modules/ta_update/ta_update.test.integr/rfc5011/empty.rpl b/modules/ta_update/ta_update.test.integr/rfc5011/empty.rpl
new file mode 100644
index 0000000..295d5a5
--- /dev/null
+++ b/modules/ta_update/ta_update.test.integr/rfc5011/empty.rpl
@@ -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 --git a/modules/ta_update/ta_update.test.integr/rfc5011/genkeyszones.sh b/modules/ta_update/ta_update.test.integr/rfc5011/genkeyszones.sh
new file mode 100755
index 0000000..4a65469
--- /dev/null
+++ b/modules/ta_update/ta_update.test.integr/rfc5011/genkeyszones.sh
@@ -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 <option>\n\n\
+Option:\n\
+\t--help\t\t\tShow this help.
+\t--rollover\t\tGenerate files for rollover test.\n\
+\t--unmanagedkey-present\tGenerate files for present new unmanaged key.\n\
+\t--unmanagedkey-missing\tGenerate files for missing unmanaged key.\n\
+\t--unmanagedkey-revoke\tGenerate files for revoked unmanaged key."
+}
+
+function sign () {
+ OUTFILE="$(echo "$1" | sed 's/[- :]//g').db"
+ TZ=UTC \
+ LD_PRELOAD="/usr/lib64/faketime/libfaketimeMT.so.1" \
+ FAKETIME="$1" \
+ dnssec-signzone \
+ -K keys/ \
+ -o . \
+ -S \
+ -T 21d \
+ -s now \
+ -e +14d \
+ -X +21d \
+ -O full \
+ -f "${OUTFILE}" \
+ "$2"
+
+ # DS for the very first KSK
+ test ! -f keys/ds && dnssec-dsfromkey -2 -f "${OUTFILE}" . > keys/ds || : initial DS RR already exists
+}
+
+function test_rollover {
+ # old KSK
+ ${GEN} -f KSK -P 20100715000000 -A 20100715000000 -I 20171011000000 -R 20180111000000 -D 20180322000000 .
+ # new KSK
+ ${GEN} -f KSK -P 20170711000000 -A 20171011000000 .
+
+ # ZSK before roll-over: 2017-Q2
+ ${GEN} -P 20170320000000 -A 20170401000000 -I 20170701000000 -D 20170711000000 .
+ # ZSK-q1: 2017-Q3
+ ${GEN} -P 20170621000000 -A 20170701000000 -I 20171001000000 -D 20171011000000 .
+ # ZSK-q2: 2017-Q4
+ ${GEN} -P 20170919000000 -A 20171001000000 -I 20180101000000 -D 20180111000000 .
+ # ZSK-q3: 2018-Q1
+ ${GEN} -P 20171220000000 -A 20180101000000 -I 20180401000000 -D 20180411000000 .
+ # ZSK: 2018-Q2
+ ${GEN} -P 20180322000000 -A 20180401000000 .
+
+
+ # hopefully slots according to
+ # https://www.icann.org/en/system/files/files/ksk-rollover-operational-implementation-plan-22jul16-en.pdf
+ # https://data.iana.org/ksk-ceremony/29/KC29_Script_Annotated.pdf
+ sign "2017-07-01 00:00:00" # 2017 Q3 slot 1
+ sign "2017-07-11 00:00:00" # 2017 Q3 slot 2
+ sign "2017-07-21 00:00:00" # 2017 Q3 slot 3
+ sign "2017-07-31 00:00:00" # 2017 Q3 slot 4
+ sign "2017-08-10 00:00:00" # 2017 Q3 slot 5
+ sign "2017-08-20 00:00:00" # 2017 Q3 slot 6
+ sign "2017-08-30 00:00:00" # 2017 Q3 slot 7
+ sign "2017-09-09 00:00:00" # 2017 Q3 slot 8
+ sign "2017-09-19 00:00:00" # 2017 Q3 slot 9
+
+ sign "2017-10-01 00:00:00" # 2017 Q4 slot 1
+ sign "2017-10-11 00:00:00" # 2017 Q4 slot 2
+ sign "2017-10-21 00:00:00" # 2017 Q4 slot 3
+ sign "2017-10-31 00:00:00" # 2017 Q4 slot 4
+ sign "2017-11-10 00:00:00" # 2017 Q4 slot 5
+ sign "2017-11-20 00:00:00" # 2017 Q4 slot 6
+ sign "2017-11-30 00:00:00" # 2017 Q4 slot 7
+ sign "2017-12-10 00:00:00" # 2017 Q4 slot 8
+ sign "2017-12-20 00:00:00" # 2017 Q4 slot 9
+
+ # 2018-01-01 00:00:00 # 2018 Q1 slot 1
+ # 2018-01-11 00:00:00 # 2018 Q1 slot 2
+ # 2018-01-21 00:00:00 # 2018 Q1 slot 3
+ # 2018-01-31 00:00:00 # 2018 Q1 slot 4
+ # 2018-02-10 00:00:00 # 2018 Q1 slot 5
+ # 2018-02-20 00:00:00 # 2018 Q1 slot 6
+ # 2018-03-02 00:00:00 # 2018 Q1 slot 7
+ # 2018-03-12 00:00:00 # 2018 Q1 slot 8
+ # 2018-03-22 00:00:00 # 2018 Q1 slot 9
+}
+
+function test_unmanagedkey_present {
+ # old KSK
+ ${GEN} -f KSK -P 20100715000000 -A 20100715000000 -I 20171011000000 -R 20180111000000 -D 20180322000000 .
+ # new KSK
+ ${GEN} -f KSK -P 20170711000000 -A 20171011000000 .
+
+ # ZSKs
+ ${GEN} -P 20170621000000 -A 20170701000000 -I 20171001000000 -D 20171011000000 .
+ ${GEN} -P 20170919000000 -A 20171001000000 -I 20180101000000 -D 20180111000000 .
+
+ sign "2017-07-01 00:00:00" unsigned_ok.db
+ sign "2017-07-11 00:00:00" unsigned_ok.db # present key is seen 10 days
+ sign "2017-07-21 00:00:00" unsigned_check.db # last edited message for check result from deckard
+}
+
+function test_unmanagedkey_revoke {
+ # old KSK
+ ${GEN} -f KSK -P 20100715000000 -A 20100715000000 -I 20171011000000 -R 20180111000000 -D 20180322000000 .
+ # revoked KSK
+ ${GEN} -f KSK -P 20100715000000 -A 20100715000000 -I 20171011000000 -R 20170710000000 -D 20180322000000 .
+
+ # ZSKs
+ ${GEN} -P 20170621000000 -A 20170701000000 -I 20171001000000 -D 20171011000000 .
+ ${GEN} -P 20170919000000 -A 20171001000000 -I 20180101000000 -D 20180111000000 .
+
+ sign "2017-07-01 00:00:00" unsigned_ok.db
+ sign "2017-07-11 00:00:00" unsigned_ok.db # revoke key is seen 10 days
+ sign "2017-07-21 00:00:00" unsigned_check.db # last edited message for check result from deckard
+}
+
+function test_unmanagedkey_missing {
+ # old KSK
+ ${GEN} -f KSK -P 20100715000000 -A 20100715000000 -I 20171011000000 -R 20180111000000 -D 20180322000000 .
+ # missing KSK
+ ${GEN} -f KSK -P 20100715000000 -A 20100715000000 -I 20171011000000 -R 20180111000000 -D 20170710000000 .
+
+ # ZSKs
+ ${GEN} -P 20170621000000 -A 20170701000000 -I 20171001000000 -D 20171011000000 .
+ ${GEN} -P 20170919000000 -A 20171001000000 -I 20180101000000 -D 20180111000000 .
+
+ sign "2017-07-01 00:00:00" unsigned_ok.db
+ sign "2017-07-11 00:00:00" unsigned_ok.db # missing key is seen 10 days
+ sign "2017-07-21 00:00:00" unsigned_check.db # last edited message for check result from deckard
+}
+
+if [ $# -ne 1 ]; then
+ usage
+ exit 0
+fi
+
+rm -f 20*.db
+rm -f keys/K*
+rm -f keys/ds
+mkdir -p keys/
+
+case $1 in
+ --rollover)
+ test_rollover
+ ;;
+ --unmanagedkey-present)
+ test_unmanagedkey_present
+ #test_rollover
+ ;;
+ --unmanagedkey-revoke)
+ test_unmanagedkey_revoke
+ ;;
+ --unmanagedkey-missing)
+ test_unmanagedkey_missing
+ ;;
+ --help|-h)
+ usage
+ ;;
+ *)
+ echo -e "Unknown option !\n\n"
+ usage
+ ;;
+esac
diff --git a/modules/ta_update/ta_update.test.integr/rfc5011/knot.root.conf b/modules/ta_update/ta_update.test.integr/rfc5011/knot.root.conf
new file mode 100644
index 0000000..5e20747
--- /dev/null
+++ b/modules/ta_update/ta_update.test.integr/rfc5011/knot.root.conf
@@ -0,0 +1,26 @@
+#
+# Minimal configuration file for Knot DNS server used by dns2rpl.py.
+#
+
+server:
+ # Listen on all configured IPv4 interfaces.
+ listen: 0.0.0.0@5353
+ # Listen on all configured IPv6 interfaces.
+ listen: ::@5353
+ # User for running the server.
+ # user: knot:knot
+# Log info and more serious events to syslog.
+log:
+ - target: stderr
+ any: debug
+
+template:
+# - id: default
+# storage: "/usr/local/var/lib/knot"
+
+zone:
+# Master zone.
+ - domain: .
+ storage: "."
+ file: "root.db"
+
diff --git a/modules/ta_update/ta_update.test.integr/rfc5011/pydnstest b/modules/ta_update/ta_update.test.integr/rfc5011/pydnstest
new file mode 120000
index 0000000..331fa73
--- /dev/null
+++ b/modules/ta_update/ta_update.test.integr/rfc5011/pydnstest
@@ -0,0 +1 @@
+../../../../tests/integration/deckard/pydnstest \ No newline at end of file
diff --git a/modules/ta_update/ta_update.test.integr/rfc5011/unsigned_check.db b/modules/ta_update/ta_update.test.integr/rfc5011/unsigned_check.db
new file mode 100644
index 0000000..cf03621
--- /dev/null
+++ b/modules/ta_update/ta_update.test.integr/rfc5011/unsigned_check.db
@@ -0,0 +1,8 @@
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+
+. 518400 IN NS rootns.
+
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+
+test. 1 IN TXT "check and change answer"
diff --git a/modules/ta_update/ta_update.test.integr/rfc5011/unsigned_ok.db b/modules/ta_update/ta_update.test.integr/rfc5011/unsigned_ok.db
new file mode 100644
index 0000000..b837acd
--- /dev/null
+++ b/modules/ta_update/ta_update.test.integr/rfc5011/unsigned_ok.db
@@ -0,0 +1,8 @@
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+
+. 518400 IN NS rootns.
+
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+
+test. 1 IN TXT "it works"
diff --git a/modules/ta_update/ta_update.test.integr/rfc5011_unsupported_key_rollover.rpl b/modules/ta_update/ta_update.test.integr/rfc5011_unsupported_key_rollover.rpl
new file mode 100644
index 0000000..77fbd35
--- /dev/null
+++ b/modules/ta_update/ta_update.test.integr/rfc5011_unsupported_key_rollover.rpl
@@ -0,0 +1,91 @@
+; SPDX-License-Identifier: GPL-3.0-or-later
+ trust-anchor: ". IN DS 13876 8 2 240B81A3498168E9F1FF85F83C24B63994D91D0569D7FB13C87E0D59AA8EB2DD"
+ val-override-date: "20190313000000"
+ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
+ query-minimization: off
+CONFIG_END
+
+SCENARIO_BEGIN RFC 5011 key rollover to unsupported algorhitm.
+
+; K.ROOT-SERVERS.NET.
+RANGE_BEGIN 0 100
+ ADDRESS 193.0.14.129
+ ADDRESS 2001:7fd::1
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS k.root-servers.net.
+. 518400 IN RRSIG NS 8 0 518400 20190326123543 20190312123543 13191 . kyHWRA9F6SKNXHKbB/roiZIUYvsQXdRzdTYZBWeiHb2puAug4h8NqdU9 yJwOpW7lzZyQILshzThh1NXueSOyJ7VYqxgAqIMiQ7hTKXvgfPsDPZYK hl05XtUZYmXQO5gdXyeKbcsI/oC4yom3IU7wt81Y18CJnlKmbY4hAf7e aDAluhbL4H9/4dXWyVBNKk8aOzHnusWjbyFdb/+UlGVEv62RhXYYMuWy c1v/8uSc1CHSgS9ef1krVkqstJtaob5lysa6Vko08XTsDKmyUJXrhhgz wzmZKaVpthAM58dMm+Twho+tLpQ2HApZUOa6Z7F9Rc2QnNLMJLRl7Iz+ fq7JBg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 257 3 253 AwEAAcVR4S9H/xPz0EZNso6tsX+z/CLhzwsDNbPVQNWddu5YP04iHKkA prBuseYVwswkQm14Jqr7u2oLOMDJ0Vn0tbw7UfBDD9nLlMhi8X3l8X++ T7xzqn99xL+8Ad0L5xQwRR7dlij8SuL0DuNhWpWmKwPDP7mI/oTNSYLD 3U/zm023Wgq+mrx+7w9Or7bh9Fo/bPN54RsTQ3BIg7LM2/wmLKtHZqiR lpyCF5gQ+eUSR6JGDfedjrvl+ywEl7dcmF11MV69pyAeASNo9+mvknwx VearXoz1KcNiCpgNmuu3lBQvoygTVmDw0RvYiWkVUmm/b+mo6hsYz6O+ XSRya8C681c=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcVR4S9H/xPz0EZNso6tsX+z/CLhzwsDNbPVQNWddu5YP04iHKkA prBuseYVwswkQm14Jqr7u2oLOMDJ0Vn0tbw7UfBDD9nLlMhi8X3l8X++ T7xzqn99xL+8Ad0L5xQwRR7dlij8SuL0DuNhWpWmKwPDP7mI/oTNSYLD 3U/zm023Wgq+mrx+7w9Or7bh9Fo/bPN54RsTQ3BIg7LM2/wmLKtHZqiR lpyCF5gQ+eUSR6JGDfedjrvl+ywEl7dcmF11MV69pyAeASNo9+mvknwx VearXoz1KcNiCpgNmuu3lBQvoygTVmDw0RvYiWkVUmm/b+mo6hsYz6O+ XSRya8C681c=
+. 1814400 IN DNSKEY 256 3 8 AwEAAZ7wwdoX/a2Va1Wx5tlTF/gVpznA/m1m7jvhnEjHCVE6iGQW3qII +tL87ScygLKV25ATPmfjIIkIIG7/NSx66eo2KiJusDjzUR8BQWcy/SHd k+r8yCifsIYTaKqgtnj91gYPoY22bG4CUt8/v1hl9FWh+C+X6occdmLr uXxeo6UOhORkM9oVcK2tOLgK1oedarg5z663JmQdEjwPkgYS7QazCAHh m3eQF8n6mD1AqKh1O6uNaVmLh3mvaI2K/0E9jRfefHJgWh1v2PfRtqlG j9idQkBZX+3IclEx8BoSXrRxVdehBvyntS+eqgx/YBOnJcdH1kOls/s2 0ZknTVQvOdE=
+. 1209600 IN RRSIG DNSKEY 8 0 1814400 20190402125328 20190312125328 13876 . qy0f6TfZls3/njJKIQlpZC3/Zq7e1O7VUFtEDiDCk8vU23PeARcMNDfb Io9VPE4MqUtHDJ7DuHUlSttLwH4KZUK7uoYW74Ii6YlnE+2pci1lj8Bn PlodQiOAhrpeH6BdZe55La5uGFE/GB7w9vbjVf6ytz5HBrdFUFoxg5V/ vUwnZS12eW0JY8HXZ7kdiyr/z9eOIRmUYIZHTXDzT5MJBYAaoDXBqE0j DKwxTn5Wx5/O2KthiRYc0j44hEQBawQnL0upBRmof+iAuUInoMBrk1u8 Ylr7RSbvS69qs8lkWGPC6VSKvAnludzcTW79K5avz3jST6rccSowuFNI oyN5UA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+k.root-servers.net. IN AAAA
+SECTION ANSWER
+k.root-servers.net. 3600000 IN AAAA 2001:7fd::1
+k.root-servers.net. 1209600 IN RRSIG AAAA 8 3 3600000 20190326123543 20190312123543 13191 . GNsOgKS3KLLHf7J05LRrLHoWWq8qtcyLoT9x5b4fk6yQvSjbRrYxjxsv kUR9f3RE+dPndevDv/GI5PHQ/UKgWeVQEvyhU5QjgveR/AvULIy3dk8f FzkOd/USy931BrOCJF2Zqzw0pHavjotdDVsoVWwwgjNlrahtKFP+e8Bm qD7C1NVrncv9bmMYNlH/ZrPniXR1pWYt4294rrSUSqoH+tVxmwdwX2kL SU/c/a4p+7ST/+GhsG26QBl0K/OJP7nAwdKP1gQBUoKDRUIzXlPdDIc8 fvDaYPq8iKYA5QHAXy3Fvd4Z02J9iTc1/vTncDJata3CNUk2B295f5F/ uk+a0Q==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+k.root-servers.net. IN A
+SECTION ANSWER
+k.root-servers.net. 3600000 IN A 193.0.14.129
+k.root-servers.net. 1209600 IN RRSIG A 8 3 3600000 20190326123543 20190312123543 13191 . fi34mMaQ+cEP1mueazJ3YXBOTKX5FGU9hZGQKMogrKLw4jwItTaxBtl2 CYCMP8B2rX9bAhBNjvqxqT5Lj1LJfomKLi+eVQhGONL3t8TgIFml6Z72 7d2qr/AiGgNH71tF/mbf5xFKrIOx37k0is3nRSmbB1FWMuvpVtlPFBey H1rAF/o69jnI7xvFu4TnQHQM+tG/NeCa1fBTJB2J02gS2XUBgPIk9f1a bkuf7nofj7tEN7+jHv2U3dDFDoMafcYIgzF/wlieqDTorBi9SkT68+nh hpJAG29d0rbG3CWUPI6Tm6El8eH+3hC6B8Emc3T30m3R5E/A4VJsbsOt vKBUIQ==
+ENTRY_END
+
+; QTYPE == RRSIG is not supported, https://tools.ietf.org/html/draft-ietf-dnsop-refuse-any-04#section-7
+ENTRY_BEGIN
+MATCH opcode qtype
+ADJUST copy_id copy_query
+REPLY QR AA REFUSED
+SECTION QUESTION
+. IN RRSIG
+ENTRY_END
+RANGE_END
+
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD DO
+SECTION QUESTION
+. IN NS
+ENTRY_END
+
+
+STEP 10 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH opcode qname flags rcode question answer
+REPLY QR RD RA AD DO NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS k.root-servers.net.
+. 518400 IN RRSIG NS 8 0 518400 20190326123543 20190312123543 13191 . kyHWRA9F6SKNXHKbB/roiZIUYvsQXdRzdTYZBWeiHb2puAug4h8NqdU9 yJwOpW7lzZyQILshzThh1NXueSOyJ7VYqxgAqIMiQ7hTKXvgfPsDPZYK hl05XtUZYmXQO5gdXyeKbcsI/oC4yom3IU7wt81Y18CJnlKmbY4hAf7e aDAluhbL4H9/4dXWyVBNKk8aOzHnusWjbyFdb/+UlGVEv62RhXYYMuWy c1v/8uSc1CHSgS9ef1krVkqstJtaob5lysa6Vko08XTsDKmyUJXrhhgz wzmZKaVpthAM58dMm+Twho+tLpQ2HApZUOa6Z7F9Rc2QnNLMJLRl7Iz+ fq7JBg==
+SECTION AUTHORITY
+SECTION ADDITIONAL
+ENTRY_END
+
+SCENARIO_END
diff --git a/modules/ta_update/ta_update.test.lua b/modules/ta_update/ta_update.test.lua
new file mode 100644
index 0000000..4d6b0ff
--- /dev/null
+++ b/modules/ta_update/ta_update.test.lua
@@ -0,0 +1,84 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- shorten update interval
+modules.load('ta_update')
+ta_update.refresh_time = 0.5 * sec
+ta_update.hold_down_time = 1 * sec
+sleep_time = 1.5
+
+-- prevent build-time config from interfering with the test
+trust_anchors.remove('.')
+
+-- count . IN DNSKEY queries
+counter = 0
+local function counter_func (state, req)
+ local answer = req:ensure_answer()
+ if answer == nil then return nil end
+ local qry = req:current()
+ if answer:qclass() == kres.class.IN
+ and qry.stype == kres.type.DNSKEY
+ and kres.dname2wire(qry.sname) == '\0' then
+ counter = counter + 1
+ end
+ return state
+end
+policy.add(policy.all(counter_func))
+
+local function test_ta_update_vs_trust_anchors_dependency()
+ ok(ta_update, 'ta_update module is loaded by default')
+
+ assert(counter == 0, 'test init must work')
+ same(trust_anchors.add_file('root.keys'), nil, 'load managed TA for root zone')
+ same(trust_anchors.keysets['\0'].managed, true, 'managed TA has managed flag')
+ same(type(ta_update.tracked['\0'].event), 'number', 'adding managed TA starts tracking')
+ same(counter, 0, 'TA refresh is only scheduled')
+ worker.sleep(sleep_time)
+ ok(counter > 0, 'TA refresh asked for TA DNSKEY after some time')
+
+ same(ta_update.stop('\0'), nil, 'key tracking can be stopped')
+ same(ta_update.tracked['\0'], nil, 'stopping removed metadata')
+ same(trust_anchors.keysets['\0'].managed, false, 'now unmanaged TA does not have managed flag')
+ counter = 0
+ worker.sleep(sleep_time)
+ same(counter, 0, 'stop() actually prevents further TA refreshes')
+
+ ok(modules.unload('ta_update'), 'module can be unloaded')
+ same(ta_update, nil, 'unloaded module is nil')
+
+ ok(trust_anchors.remove('.'), 'managed root TA can be removed')
+ same(trust_anchors.keysets['\0'], nil, 'TA removal works')
+end
+
+local function test_unloaded()
+ same(ta_update, nil, 'ta_update module is nil')
+ same(trust_anchors.add_file('root.keys', false), nil, 'managed TA can be added with unloaded ta_update module')
+ ok(ta_update ~= nil, 'ta_update module automatically loaded')
+ ok(modules.unload('ta_update'), 'ta_update module can be unloaded')
+ same(ta_update, nil, 'ta_update module is nil')
+
+ same(trust_anchors.add_file('root.keys', true), nil, 'unmanaged TA can be added with unloaded ta_update module')
+ ok(ta_update ~= nil, 'ta_update module automatically loaded')
+
+ ok(trust_anchors.remove('.'), 'unmanaged root TA can be removed')
+ same(trust_anchors.keysets['\0'], nil, 'TA removal works')
+
+end
+
+local function test_reload()
+ ok(modules.load('ta_update'), 'module can be re-loaded')
+ same(trust_anchors.add_file('root.keys', false), nil, 'managed TA can be added after loading ta_update module')
+ same(counter, 0, 'TA refresh is only scheduled')
+ worker.sleep(sleep_time)
+ ok(counter > 0, 'TA refresh asked for TA DNSKEY after some time')
+end
+
+local function test_err_inputs()
+ ok(modules.load('ta_update'), 'make sure module is loaded')
+ boom(ta_update.start, {'\12nonexistent'}, 'nonexistent TA cannot be tracked')
+end
+
+return {
+ test_ta_update_vs_trust_anchors_dependency,
+ test_unloaded,
+ test_reload,
+ test_err_inputs,
+}
diff --git a/modules/ta_update/ta_update.unmanagedkey.test.integr/deckard.yaml b/modules/ta_update/ta_update.unmanagedkey.test.integr/deckard.yaml
new file mode 100644
index 0000000..eab0592
--- /dev/null
+++ b/modules/ta_update/ta_update.unmanagedkey.test.integr/deckard.yaml
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+programs:
+- name: kresd
+ binary: kresd
+ additional:
+ - --noninteractive
+ templates:
+ - modules/ta_update/ta_update.unmanagedkey.test.integr/kresd_config.j2
+ - tests/integration/hints_zone.j2
+ configs:
+ - config
+ - hints
diff --git a/modules/ta_update/ta_update.unmanagedkey.test.integr/kresd_config.j2 b/modules/ta_update/ta_update.unmanagedkey.test.integr/kresd_config.j2
new file mode 100644
index 0000000..263404d
--- /dev/null
+++ b/modules/ta_update/ta_update.unmanagedkey.test.integr/kresd_config.j2
@@ -0,0 +1,72 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+
+{% for TAF in TRUST_ANCHOR_FILES %}
+trust_anchors.add_file('{{TAF}}', true)
+{% endfor %}
+
+{% raw %}
+
+local cb_counter = 0
+function inc_cb()
+ cb_counter = cb_counter + 1
+end
+
+function check_status(state, query)
+ -- status was present for 10 days
+ if cb_counter > 0 then
+ return policy.DENY_MSG('check last answer')
+ end
+end
+ta_update.refresh_time = 12 * hour
+ta_update.cb_unmanagedkey_change = inc_cb
+policy.add(check_status, true)
+
+-- 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
+
+policy.add(policy.suffix(policy.PASS, {todname('test.')}))
+cache.size = 2*MB
+log_level('debug')
+{% endraw %}
+
+modules.load('hints')
+hints.root({['{{ROOT_NAME}}'] = '{{ROOT_ADDR}}'})
+
+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(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 --git a/modules/ta_update/ta_update.unmanagedkey.test.integr/rfc5011 b/modules/ta_update/ta_update.unmanagedkey.test.integr/rfc5011
new file mode 120000
index 0000000..b467cd6
--- /dev/null
+++ b/modules/ta_update/ta_update.unmanagedkey.test.integr/rfc5011
@@ -0,0 +1 @@
+../ta_update.test.integr/rfc5011 \ No newline at end of file
diff --git a/modules/ta_update/ta_update.unmanagedkey.test.integr/unmanagedkey-missing-monotonictime.rpl b/modules/ta_update/ta_update.unmanagedkey.test.integr/unmanagedkey-missing-monotonictime.rpl
new file mode 100644
index 0000000..334d959
--- /dev/null
+++ b/modules/ta_update/ta_update.unmanagedkey.test.integr/unmanagedkey-missing-monotonictime.rpl
@@ -0,0 +1,758 @@
+stub-addr: 2001:503:ba3e::2:30
+stub-name: rootns.
+trust-anchor: . IN DS 15116 8 2 6743F544CF087FE23094D4FC1305F6B9C4EEFA2025B4FC348A622CE202F5DD4B
+trust-anchor: . IN DS 45050 8 2 DB11ECB4E98390817B2D4BBBE73D7FDFE7ECC418E006EEF6EA05044E565A3733
+val-override-date: 20170701000000
+query-minimization: off
+CONFIG_END
+
+SCENARIO_BEGIN Simulation of successfull RFC 5011 KSK roll-over during 2017
+
+
+RANGE_BEGIN 20170701000000 20170710999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20170715000000 20170701000000 31500 . dPoTEA/PKBUCWNsGLH27WXLRSoQBpgw4 R0cAGdXZHVlYTZzfaHAmfgvoZOeoT0pt 11584nVWOTJDH3p0bDgJBTLsCbs3IeTx SnrZQxWP6/WvyK3NwAxI9q8amEIf8Rin JQCiTOvxdVnb5lrDZt3bzgCTvWm0EqVy 4WjewqhhduiWYq5sRjqDMSKbbP9zGE4l 9gaCgIJA4zPvcedYpqQpmDnITM1SlpLH /JtpPVVstLTV1MmfmvY1ay3FMYRMr0FU 7FKHtKmY+RSaRWzno3ghWPi3PZrc9J5Z dyS8EfD+dpFibv+auFp43NnVL+mISQ+/ nfKLf8PYT8DDBog7OlaCsQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAecpzqv0lz4aTPxYhm+LlvNVtLCq LhWvBuUdPoOxVO99j/KDXEdslHRfRLlf ZHi5KBfGLBwW13VeO9bzv+/62PycepCH WyE4I+IHSgVau1vkCtfRtX5wp8EMPyqU MtHPvuForHdp3ZWepuZh8i8TUgEGn3F+ ISS6yyy10gR8fPTSmXui6ZWPk+yarFNC /BosJPw0Y5gkPIltk9oHeWcTTwd1XpqP b2Pm4FdRN8rpJGvx5livyVdrEAXMnlV9 PqCtJVLBq4HN7LJmXde+ODLmzVP1JgaO qC9vlu2vKL2AlKW5BCCL9gt0+XeFU3KH qrs5GgnX2xVKoIzA9Sraepg+3i0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAa4D+Q6MqSfx9vyCCGlOHYAbbxo9 buybBLpq2Q08j+cUDc1/pSVSmYfJ3Xup QcFc7Ney7BUrPqRfzQyL/NHEtupWyLJ/ gstz55oiEXw8RCsYZH+nay4nPthtQ+nt FSAZpw32ED0CHH87ENxxxfkB1hkT78ru RG5pYT+qGeSDXGrkYSHGNSfS7YB7RX9h Q1/RM8ef/QyKqLkFli6OPvLa6u0eCAI1 IHvHbQvieoVioWaJ7jmKxMwIONQfMbYH 8Hs2JeBgNhaH4qHDJdLJePJNuht6NZ2K 0vfcDpkDbj/6r9XpJiEEjB7iFIfhc/ya Tkyu7tIUwXs7tTDALi7gpOk3kL0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcfEF/AXBm9m/eLNdnsq4O5otNVD zNmdViy4pC1paInyq7tgy8blsivvpy7X Hh/xnFPCX6XRMUxxpe7Afuc9BITwepap rIdM/j4QOIeuQbLSxhzay1jdsCWMHFQ3 hMQeXZ8X05oxnIKihnfISyhqyaNdlY2k 4ApVHDF6ugDd1+Zu4T/5M9DIhxCTHpVo hctCzkgFP9tRocAlIwptcoZLdR3LxdXk OJ9tfSOuqHnQ4Zh8Aj407rVtz6CWQGyg qO+fb+Y98DI3V7IYisnW18r7QorMUycA OuDnnlZSvkrcWgtpuyylTsqhs/3+73iK kb0QUKscoTeSLgeoocFN+r8ZfaM=
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170722000000 20170701000000 15116 . mkcwXhObb+tZcwBexTnJQU9xFXI4DYLH P3J80/mYHj3r/6542Wd4+IscpVIFhtxf WsFQhvzS7IZevSbOojRYPjLIKpaa+WnU zYOi4n5vNq5PWzXd1Y5tHqDCxF6fQB4H SfI2GB/a37s3GnaEWA+ECzIZiC0xxASt obIkWkYoqLaI6aMILOSjXHJxKbY1wNPm SGm+RohpjYmgLzFAD3NJNqleYexcarrK +pfbeBoQFmAq3wJqCRnPYfAfivFA2mDN 2PMI+K6hx1Wf/NmmPM2a+UoPREashEe6 exbC7XZbs10aP8gCgHkGmGWgSlbLqFpx 9f2GBjotJsEbJCumiT6hpA==
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170722000000 20170701000000 31500 . DOjVyLlWPX1DpvYhWHoh/LHXqTJK2SIG yp7iLXnmkb3SCFslOcVwYm2m0xfRplZo dA/HhFdT+cJ1wlXZSXBm17QgDK9hNhnm kmstpbv+xj/Pf31TJkHRS2Mn7FtQTRKE zJGMJvnfgY2PMpAwVyDMc2psA5AJbx47 uS5+JJgDcnnfM8OoTkT3RkHKV942nWTo lhpyBnj1Ymzvlc1DxVW1DIJjnQjoU9PZ sJT4NESMJyF/cskELuSqyqvrUC7WAnNU hhE0rcTBhxJpA9iaCW+Jn/CGTxR5Hrmq Vlyf4ctduNwuOl+puA9RNj7+JnBX6EN+ KM1NqxpSI+gcHSEj9MXejA==
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170722000000 20170701000000 45050 . mi9PqTNCsum4+302sDVzJW4RJsW3Gtec 3+Z/5CKa5cP0l+w/5g6mWV1eBeT3ZeF7 kkleUP7chgtHmh1d/q24OE7R+R+iZrf7 OP1SJRN4cxeliM1mJFGz67YFkWzqzwiU +WbuAg/HBgGmtSO/6QlwRtBEbx8LwZx5 6OIHEUVCYEPdk96iU6wAcgc9RkkBVrJz M8UEbSFI5WNbWKJMfT6BqgFPd84AslqN lxZm0WNGP2xdsdVR9TSx2dTUiXdTymi/ x61AFQKn6hlf5cY6LZPOR5JquqXtAgV2 xf3XtOjnFaH81IECqXA/cRrR7Y3Oxl01 Fl3A9C0BBpOH2ngQwHyhiw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20170715000000 20170701000000 31500 . fWSlKWRwiYLYHxZi7z/0lFEhGd7uJrnJ BCe5cY0MVWuhNKx/k331hulrolI6NBrJ Tek6rMtkHpg4fF4x8yTFYS0lNhMI/dJk AZP+7w16T4YCtEwQdqfaPLV+Q0xxSGyI f1tqkQ7iLzrjBHc91IdSKysY0N/z3A0H VwpELudEq6P840ocrolQpxHLD0TgvrXe PO4+Wrg9G122fpvB7WGvufiKRyU2qVGZ hUa++t+JKe8sdCPXe8fe+njD6iOBOBUx WMXxJ92zV92KfxdyvwEaFyPVUAEmHFyc zUQHMmPPAoR2Uy7b4etXlBN5pgYwYk/O tmAuyKNJg38Frdz/TK7F3A==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20170715000000 20170701000000 31500 . mA/R6+XmbyI0ljRHtTaDpu1fgm7/XGvE UMwmNqieJ/z7RbJ38yvGnfKDERhKBhQr 7EPdCSldpwg8LXx70Luu/kklvvxn20Aj my8hWGW4Hsl+C2qVZ10qzHxbx0lDgnK6 JKYBLdCnDXHS8KfXkrFIeWFdZWGXpcOt mPppEG4sp8OhpVohgiU/WGZQWs3TjeO0 k85iq4g5QLOeQnRKlCaX2S6wlNOTfcvi j8iuUjcMstx0z5MWMn9RuFvKYXM0aWwT d+QMLBEt16LBCF52mxKy9RD7Wi5zIo1T SfxAxvXofrsnw48UZ4PpnE0K6bCzW//M rJMycfKaqjPDN2nabZHIqQ==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170715000000 20170701000000 31500 . xLCTTIMJwqN9qDMm4tggaqUtf5pq7V5/ 0n6YT6Be9lstyIVL32ra94lJtjeF8gUA Ys1ugC9y9j88pYLcD+/IWSHvQ6FhZU73 XZA5wm6da2NQnyOXZSEofz/E7Arlmy7y Dojb9zmzgaUEEqnvQ4koqhpENbq4oTSp aJ7/UpsRrXUwrwJdxPnu2EW2hCgyFQ2i uAra/Bmzl046hAOTGzjUbbNgJt3JaLu4 qYKNcs/yUqGoZdcU/OOYfxFoAI1I+K/s FqCoVYV4yjQ6sB0s3isjoJT0fN1/QGMR 6a+ebQJ/B0B3Ch+VcC4abpMkWafTrOY/ J2KTNzvbXD9X2as1y6pa+w==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20170715000000 20170701000000 31500 . dPoTEA/PKBUCWNsGLH27WXLRSoQBpgw4 R0cAGdXZHVlYTZzfaHAmfgvoZOeoT0pt 11584nVWOTJDH3p0bDgJBTLsCbs3IeTx SnrZQxWP6/WvyK3NwAxI9q8amEIf8Rin JQCiTOvxdVnb5lrDZt3bzgCTvWm0EqVy 4WjewqhhduiWYq5sRjqDMSKbbP9zGE4l 9gaCgIJA4zPvcedYpqQpmDnITM1SlpLH /JtpPVVstLTV1MmfmvY1ay3FMYRMr0FU 7FKHtKmY+RSaRWzno3ghWPi3PZrc9J5Z dyS8EfD+dpFibv+auFp43NnVL+mISQ+/ nfKLf8PYT8DDBog7OlaCsQ==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20170715000000 20170701000000 31500 . czXWsvkZgPgtICpYspe8ny5sloUCCQ01 S4CwdGKFkyoOh0jvnL/iGa9X4TtTeTry 2gtT8+Azuvaq/rXXG4hrSmvQloCyDMtD Qy86So9w8nMxGBnXW/fHqt0mgYBoYmw4 PYM6vrOwMOxh/db8qMdHz/MdHPUWxUNG t+2LJ55RwUlb4YFtLzcNUe3CPf7nydSY y8lDbDz6GzbwGz65mUN8/rjeEOaRaQON rb+1ETXGKX7aFga/0t0GBTEjkwAKbaV/ 7nBk9CYR4IpeIIMuHPQiSEenzhXEjDMx Z6pue7+DrFuw/yFfvNeArQ4XLPglMzM+ YigxHXt8Z1d8CJzeImCWmA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20170715000000 20170701000000 31500 . mA/R6+XmbyI0ljRHtTaDpu1fgm7/XGvE UMwmNqieJ/z7RbJ38yvGnfKDERhKBhQr 7EPdCSldpwg8LXx70Luu/kklvvxn20Aj my8hWGW4Hsl+C2qVZ10qzHxbx0lDgnK6 JKYBLdCnDXHS8KfXkrFIeWFdZWGXpcOt mPppEG4sp8OhpVohgiU/WGZQWs3TjeO0 k85iq4g5QLOeQnRKlCaX2S6wlNOTfcvi j8iuUjcMstx0z5MWMn9RuFvKYXM0aWwT d+QMLBEt16LBCF52mxKy9RD7Wi5zIo1T SfxAxvXofrsnw48UZ4PpnE0K6bCzW//M rJMycfKaqjPDN2nabZHIqQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170715000000 20170701000000 31500 . xLCTTIMJwqN9qDMm4tggaqUtf5pq7V5/ 0n6YT6Be9lstyIVL32ra94lJtjeF8gUA Ys1ugC9y9j88pYLcD+/IWSHvQ6FhZU73 XZA5wm6da2NQnyOXZSEofz/E7Arlmy7y Dojb9zmzgaUEEqnvQ4koqhpENbq4oTSp aJ7/UpsRrXUwrwJdxPnu2EW2hCgyFQ2i uAra/Bmzl046hAOTGzjUbbNgJt3JaLu4 qYKNcs/yUqGoZdcU/OOYfxFoAI1I+K/s FqCoVYV4yjQ6sB0s3isjoJT0fN1/QGMR 6a+ebQJ/B0B3Ch+VcC4abpMkWafTrOY/ J2KTNzvbXD9X2as1y6pa+w==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20170715000000 20170701000000 31500 . mVYjV/8KTVvel7+USquoejpj3GYlnLmm 4AUifG/JHOOHsxak7JpRmoGx720oYBLm mLYuxIU4RB1GmZSb8lQH8Nv/vG6yOtKc 7sajnu3M6edPSM0+6Tsh+01G4s5rhqUe 8K5vc6s/HmWziHKpcWTGU6mq5OrYpcZD WmMdu7S75rcZDqX5ZbjrB8liBT528V/z 3nWNTKevL+vGYkwXfxiEYqTfbO8rlLZS Gqj/gAPFrojtu1qdmeJ78YE5ZiQwnAqz 5jAdsyQCFaX/tn4UMwmxyIBYdb9OrU9S Ec8TbRS24s9aaPFEmPJNHX0HYD84hMNY IF+0igXM6HFeTxVO0F6TSA==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20170711000000 20170720999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20170725000000 20170711000000 31500 . fna14VuFmwBxUnBYNILk3jMd25fjhUm/ hkbF4bJxDnJk+qV6pSCMPAkINYbZb+UF UqtaIqOy0W3gSAxKPObFOjX+KExtyjRq AQPKTBRTDuYWndgEfVMNe9x9DkEqv6Gg fygyGD1kRAc2Rum5iOF5G/kLrGEIuSiq AJ/LKqF+hlPxC7E1HttlSVPCeEnW+Rrm pbiniH0hFqYQhOlPzHZ6aX8A2oERn1IS 4+XwNBQzLjqxcOHyf2EhlwCK2tYhMjAU NkzK4kcHK2r8jHVh+VBNF6kgP55qKwWz +fE13zkkkVP4f1JZtzfrC0TUIRQAztJ4 0vWR3XEatn8cNlobKfiGPw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAecpzqv0lz4aTPxYhm+LlvNVtLCq LhWvBuUdPoOxVO99j/KDXEdslHRfRLlf ZHi5KBfGLBwW13VeO9bzv+/62PycepCH WyE4I+IHSgVau1vkCtfRtX5wp8EMPyqU MtHPvuForHdp3ZWepuZh8i8TUgEGn3F+ ISS6yyy10gR8fPTSmXui6ZWPk+yarFNC /BosJPw0Y5gkPIltk9oHeWcTTwd1XpqP b2Pm4FdRN8rpJGvx5livyVdrEAXMnlV9 PqCtJVLBq4HN7LJmXde+ODLmzVP1JgaO qC9vlu2vKL2AlKW5BCCL9gt0+XeFU3KH qrs5GgnX2xVKoIzA9Sraepg+3i0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcfEF/AXBm9m/eLNdnsq4O5otNVD zNmdViy4pC1paInyq7tgy8blsivvpy7X Hh/xnFPCX6XRMUxxpe7Afuc9BITwepap rIdM/j4QOIeuQbLSxhzay1jdsCWMHFQ3 hMQeXZ8X05oxnIKihnfISyhqyaNdlY2k 4ApVHDF6ugDd1+Zu4T/5M9DIhxCTHpVo hctCzkgFP9tRocAlIwptcoZLdR3LxdXk OJ9tfSOuqHnQ4Zh8Aj407rVtz6CWQGyg qO+fb+Y98DI3V7IYisnW18r7QorMUycA OuDnnlZSvkrcWgtpuyylTsqhs/3+73iK kb0QUKscoTeSLgeoocFN+r8ZfaM=
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170801000000 20170711000000 31500 . IGL5Fg08GJae04Y+aAy7xWGb+0pnXPnB XlFr16x6V2X40G7QdVEwATK+FO8T93m8 lgD+ixOX+6Wz67tlEOlCjdTl8BJUwlSk HB3+s+GZnotwkXnyTeWFrupOPNdkSTT4 wDMZLsNVKWCsrdBV5o4kwQedjFbeEQKC KVaDbSAZ6Pjr8F+uCkaGGmKYq3tvU5j4 Gd8JXLWASqj1AT5lJjNiVgACbSLpReVV bpxRHvdoKPgog/5INATqZS/lR5SXMEik DyEOv4CGl4pi+7yB73i6r7V5SWCMp8Jl 8RVLUFlMMqKg+SScybWkVSMJJyfnW1/j EpIO+3LzTKoZcdwhAJi1+A==
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170801000000 20170711000000 45050 . pujHutwLhvT+8bki6jnCpW5UtK8xbruO /Wyg25ABJE+V0Qjqss3UbmqtRjaCT+b5 ZORlk8N7ZmuRdxQM++qT34UlmLtT3D43 DRRMYdCCnnBKqZhpOjZMm+ry1xNj3qUb izl74tPaGhibxzlaLdld2FlaBOKlJXeC y2d05I2nXICzsjb04cFDMskLeHMq4P3M AMf6RYqAlAPoLeeaW6Q2m+D8qIa3epIs eHJL6GhQYwpg6hT/fACOYX8YqPQTvhmC 7PVmRghYYeUb6am2kywJaMZg+zzyemVf R9HOYSEquyRvjj/1IbbanpdEZh6bO/et pIOK9bTqQpvg6pkZ8x708Q==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20170725000000 20170711000000 31500 . zcWreQ7KQq9QK20WvzbNjfGrVEO15RaV YJv1QkgPj8JlgNedGg4pHvD72c9pVgqY DBuXQNfV0m1l+5LTtLAoX5s7t8PzhljQ S0t0RNkZXpQji/FtQQO7/jXt8bPTRDi6 wcn3uhu2fLuXEpXYVdFCC1s/8//9vPUU BeGY1vpdRZV+wd8HgnkqAoTyOKl7csSI cOYyhMpfwfiXohFz3AxG67Qjwj7wzot3 RsS9CDpBgwoWBTmi5dnPD++wbn0EXgmo w+tDGtk+PQBC9FPxxmVmaqHlnRM1QGqi ivS1MGGUzXXkZdzWNsdc/ZXQmJt5F3jX 3oZDR9pyAAky4tdebyuRlA==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20170725000000 20170711000000 31500 . AcVAjovwI9gcS/v+36yqoC4myF2iCLp0 65URXF3OEnpG9PRoQYCHhY9NuC9MaX02 gR/FfUJcHQT+NKKLL3WXZJ5llWKuNQa7 aPJV4ygrOjQ7ypehEYRVBygvDPfFAGNj 8Dxd0rvjpWtaMz5A5DXBLa2W84iDrOhZ RbnDNREItVEZGG9BN26T8me0YJoYOwx9 BUOC6FfAoupZJd/cbPcsp3kzIJCCP3Vy H8DFkYeno8yaWhoEuKwleto74nrsl8Fl ihBXEqqUResGkjMyJedgvnPtv/5kXmXN tvuNBw71Y85FkRgbCQKPJdaMb/Gq+K8a 9aVGNH+/XHqPDNZ0oJF4TA==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170725000000 20170711000000 31500 . 1oeKgUeMR97GMVAZL8pgbBcgw6Kf4iu6 9nNjKtrHntavlQpBavUM2JX/nDyeBpcI C1oo0+GuqA5y5MfK2nmy5gQgXyN4S0Ag aXgrHZx6vgvFp5RXB7BY8leSTt0Pl8QC ugb179QYbzIxnHxZ+GJMQdA8Jb8FpBRH yVXhrTwypjW1Wdyoeo6TdxAikuF/esGp oZCg8V9WG2VHDmtU04uIpocN2t8mk8jA TBHKW3z0Vak0/4j/cvj2Yi+4dFivs88+ asgCsfzExN9r++BLm0uif+29pFgqh+yx pVjQZ9dUgQdkhnnJRmgwezpHei/c4/ZW 9HAKvp3qdPoPvIo1/dQClQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20170725000000 20170711000000 31500 . fna14VuFmwBxUnBYNILk3jMd25fjhUm/ hkbF4bJxDnJk+qV6pSCMPAkINYbZb+UF UqtaIqOy0W3gSAxKPObFOjX+KExtyjRq AQPKTBRTDuYWndgEfVMNe9x9DkEqv6Gg fygyGD1kRAc2Rum5iOF5G/kLrGEIuSiq AJ/LKqF+hlPxC7E1HttlSVPCeEnW+Rrm pbiniH0hFqYQhOlPzHZ6aX8A2oERn1IS 4+XwNBQzLjqxcOHyf2EhlwCK2tYhMjAU NkzK4kcHK2r8jHVh+VBNF6kgP55qKwWz +fE13zkkkVP4f1JZtzfrC0TUIRQAztJ4 0vWR3XEatn8cNlobKfiGPw==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20170725000000 20170711000000 31500 . kMVZp4DiHSXYRBgio4X+4nK32ITxnk7B uUwJHT9UIeQuyc0ag5T4sgNo73dcWAZF Jy+oXiv/5RkTA1DuFE7xLkgu51DwUo+E QmIFYPiVB8WYijF/c1aawx3mawEnERpS MvvX8e9rvtW1wNpNWDYjOOvR9S8HnQk/ MbSC96Non8LDX9chdITYlM33qVDDFCyh nd5PDe79UN8HvKXhhtin6h5LyAwVQolz 14/fEGimtHbEYCni88j3vWRKjt/vs6S6 Fl+OJ3NoSVr52BbbnaI7A06iXKGTyLSg HE1G6K6ENMfrhz3HIE5ze+CRZN5/WNtS 6A8+j3QqphFIouHqAPqw0Q==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20170725000000 20170711000000 31500 . AcVAjovwI9gcS/v+36yqoC4myF2iCLp0 65URXF3OEnpG9PRoQYCHhY9NuC9MaX02 gR/FfUJcHQT+NKKLL3WXZJ5llWKuNQa7 aPJV4ygrOjQ7ypehEYRVBygvDPfFAGNj 8Dxd0rvjpWtaMz5A5DXBLa2W84iDrOhZ RbnDNREItVEZGG9BN26T8me0YJoYOwx9 BUOC6FfAoupZJd/cbPcsp3kzIJCCP3Vy H8DFkYeno8yaWhoEuKwleto74nrsl8Fl ihBXEqqUResGkjMyJedgvnPtv/5kXmXN tvuNBw71Y85FkRgbCQKPJdaMb/Gq+K8a 9aVGNH+/XHqPDNZ0oJF4TA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170725000000 20170711000000 31500 . 1oeKgUeMR97GMVAZL8pgbBcgw6Kf4iu6 9nNjKtrHntavlQpBavUM2JX/nDyeBpcI C1oo0+GuqA5y5MfK2nmy5gQgXyN4S0Ag aXgrHZx6vgvFp5RXB7BY8leSTt0Pl8QC ugb179QYbzIxnHxZ+GJMQdA8Jb8FpBRH yVXhrTwypjW1Wdyoeo6TdxAikuF/esGp oZCg8V9WG2VHDmtU04uIpocN2t8mk8jA TBHKW3z0Vak0/4j/cvj2Yi+4dFivs88+ asgCsfzExN9r++BLm0uif+29pFgqh+yx pVjQZ9dUgQdkhnnJRmgwezpHei/c4/ZW 9HAKvp3qdPoPvIo1/dQClQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20170725000000 20170711000000 31500 . IflrU7TCMFd6L9qaQEgwSobKiGUfj2YZ 16MFrgiTl7mqZSjjS/Jf/dMXRJv/btan /XDT1yoItIcQgio3htKf/7Lo95kbUelU jSc6GvFEPoiceid8QigIAuSpKKH4eCh3 G/KW14UC1yHuLT3uwO8YLR3GNeO61sGB gafeCa4ePFSXszOcfq8ZfMKHTs8H8l1N 39rrN1rrcdsp2bVUA37BpJ2TI8eCf/K4 r50BXWE8db4PiXi3PHE1o8KEdhMKo5ku DyfXEuC7pgR0r01jB3nozb+rNRfF/VFd b3Zv7Fb+b1k79uUNtuTY24xTkxSeLSWy tesX5w7ReAEO+L9Mkh6N+Q==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20170721000000 99999999999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20170804000000 20170721000000 31500 . lPXV8zPAsFCStPWYakYlvnOjRpqJm4RX 0tlJQAyDiQ7WwuuVqAYQefZx0fhveIHw Ntn2W69QGU+qZKUujrmSWg7KgK0+sOQ6 eLqTi+F0rg4qg92Q2Fh4DpHFOn6CzSp7 y/Ndyepli+B/xnRuvJsZiR0bpNdzpDSP uN9boISWMqoOD6EgL+2N1qJ4Bw9J9STe Ay7ZZz3gJYX0Pq0m+ndEChNbpDayB3Gz FeaicTl2Owqjj4XUUPlAUD5udlen688g vGqj/MS3rSs9Fv5QrvmP/fOKEw5wO1/7 UMS86BU+v4vnZ3EamXCnvfWTQeYfqy2Y wnzpucnMFI/11O723xtjTA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAecpzqv0lz4aTPxYhm+LlvNVtLCq LhWvBuUdPoOxVO99j/KDXEdslHRfRLlf ZHi5KBfGLBwW13VeO9bzv+/62PycepCH WyE4I+IHSgVau1vkCtfRtX5wp8EMPyqU MtHPvuForHdp3ZWepuZh8i8TUgEGn3F+ ISS6yyy10gR8fPTSmXui6ZWPk+yarFNC /BosJPw0Y5gkPIltk9oHeWcTTwd1XpqP b2Pm4FdRN8rpJGvx5livyVdrEAXMnlV9 PqCtJVLBq4HN7LJmXde+ODLmzVP1JgaO qC9vlu2vKL2AlKW5BCCL9gt0+XeFU3KH qrs5GgnX2xVKoIzA9Sraepg+3i0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAcfEF/AXBm9m/eLNdnsq4O5otNVD zNmdViy4pC1paInyq7tgy8blsivvpy7X Hh/xnFPCX6XRMUxxpe7Afuc9BITwepap rIdM/j4QOIeuQbLSxhzay1jdsCWMHFQ3 hMQeXZ8X05oxnIKihnfISyhqyaNdlY2k 4ApVHDF6ugDd1+Zu4T/5M9DIhxCTHpVo hctCzkgFP9tRocAlIwptcoZLdR3LxdXk OJ9tfSOuqHnQ4Zh8Aj407rVtz6CWQGyg qO+fb+Y98DI3V7IYisnW18r7QorMUycA OuDnnlZSvkrcWgtpuyylTsqhs/3+73iK kb0QUKscoTeSLgeoocFN+r8ZfaM=
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170811000000 20170721000000 31500 . taRMDz94VQ0lr4fUz9RcksuoEmSJ2dba BtEzOdj1GQHLUvIeY9n5kGqSvWA/ijSQ i8U3gs3KBWUGFZYVLNFRWzLEOumNx28a i+M2cXSo1z5lfh/kJVPrsV7lfNdilk1X yIDYDSKLrdEhfPly0Sm1SeF1AFdUZ5Br acniNZ67nO1z3qNQOoHnt/ZXNAkfUQoJ Iu5w87qxDVXT4ju5bDH2ozscPrbO0cbs Oq5S8H17O8d/njPdxAcYAW/aIjRcKOaD S1WDweTES96JURAo9I52JzkzPcnv2RW5 BiVIzI3o6gOVDTpzIeQAonGpT5QljdUC bUCYXkV+c6dIzA/aIVEwtg==
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170811000000 20170721000000 45050 . QFDngPrnZLHICu5Sga7Hzmw4e4I2IL8O ED3u/F04CycjN7QgwcQyNr4tHrk94W7p 16fvkeORpG8BuLbHCium23u/7V5TJOOD MeyhO6V0wBzpmf7xrYfPPmHStTk1hgIE gkevsV7MMT4Kpc1VAGAHEIODtXwlQg71 YOITOz4tGgovIVej2qqLjYcYq6HfEMx+ Oytp87hu1+bGOjlbo+mfiEv/vRNejqVj rgq6ixkw3DAsI+Pnr49oolDApN6/wofd qh3OudgyCQVmxArXREBFrmcHL7lWy3A9 /NTaFtsCwAZHWqO3BeoU0qWfpo8TsEow tsW56HdwhYV5CW9js+VwcQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20170804000000 20170721000000 31500 . JVme/rkfApeeE/Op6KYQuRGj5UgCl1L7 sMsCOCgXvQp4Ch4T2t2Fou+aC0/QKtGz nVxZhT9euD11cdsCGl+MVhRfbqfKeaSk LczLWw3CcJJHNa3OkKnbTlVMjB/6m08c Vv3tUsySIrGU3Hu06n7UBc7X88jbJ9hy EVWmIQ+B7goans7PWCuifMIqD/ELPFPd uCrSPn4GOdsTdnHmrXfRSBG4keUuGu7J ZZ5sWUmffa09uJUTWxC+0idzuCR0t9KF 07PPwcdst6z87Vc0ScTdDBt3Dim4+fim azBJTnEriTNyXBCIo+/O7xpMvPCTnIsR L/5/gfcKJppAjmwc86eOKQ==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20170804000000 20170721000000 31500 . fSxz+ikGl01EsGT/yLc4BgPWf+KePqx0 TfWiXV1dZjiX2xajzeVp75fv3TsFkgWu zsAYmVTeOdcqWSaYrXarBi8p0tu3HWUs XB1qBLHcW31rfTYI28NsurIPSZDG8ZdD No7LQ32eO9YxzsZv6Wbe6XGkuYLmWevn Spy2sPobBe/UH585XyuZds1U3VLJ11Ys sOk1EUEF4bQswmVRjNX/Snr1PEH9pqDY BJAh29t1VNAyhlEibAztATVJqayLKxGK SCDVmzqIQoafupmcl6SruyhJJx8m6SEa HyknUxV5JC4vDSObwv2INc9nPH9vODF4 dhzHYbW8FA0zON79JuNUtg==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170804000000 20170721000000 31500 . 2z1lZl81ufBgiUsfgVnn3524CNxoFA5T 8Wfv8Up/484yxitiEFXCR/AohncE2kRH pWN/9UmuzUALJk10SYcD185lBoqhB5iI NPllRyYNYcnuS4Oq7sp+52tJRxqkMIWX V8v09rjhQzTx24UNNB5uZncOmZA0e9P3 KoeR1wG/Eh0dUESrjiwOHvzfmOffAhy/ ymY4XE5KZEwSxKaUfoUT8VgiKLxnNqwB UiCN15xSCAQBpHCxV1owQFtqYfKzNiML jL/ZjznEQfcvdGvfVAM15nLg6VNEYL8G LxP2CgH5NZpakCFRLD3Bos8F3w6WBuME 3CXUo/gqNlV+EQ4R823SxQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20170804000000 20170721000000 31500 . lPXV8zPAsFCStPWYakYlvnOjRpqJm4RX 0tlJQAyDiQ7WwuuVqAYQefZx0fhveIHw Ntn2W69QGU+qZKUujrmSWg7KgK0+sOQ6 eLqTi+F0rg4qg92Q2Fh4DpHFOn6CzSp7 y/Ndyepli+B/xnRuvJsZiR0bpNdzpDSP uN9boISWMqoOD6EgL+2N1qJ4Bw9J9STe Ay7ZZz3gJYX0Pq0m+ndEChNbpDayB3Gz FeaicTl2Owqjj4XUUPlAUD5udlen688g vGqj/MS3rSs9Fv5QrvmP/fOKEw5wO1/7 UMS86BU+v4vnZ3EamXCnvfWTQeYfqy2Y wnzpucnMFI/11O723xtjTA==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20170804000000 20170721000000 31500 . 3P8Vf4KjZRcPyoAJpHAUr2HEx2t75e3K kcc4BnzOgSubeSOrDm33ncnUhBx8tKSD G4oth7beyxmVy9yCWtiHyk+LjfOnyMhP FPlX+/Eflz3Ehri+8qdR6RTnRpp6NVpm aVeocIeONo1NKye8J3PLRZxyxNxxoTSM WRHtWfGA+I3IoDe/fY9Bu5k+HU5pqHgf iSHMo21B/jLHgbPXerOUfIUFUr8UExBt faQC743Dc56zR2IO20VrBAJQueWGqFdE ZUDwYks1PZulj6oGNSd5+PNqB2cVUznW evt3wkQyol11uXNjEv8TwoCjTbks/tmt wUZ1kZm+8PJc1QbooX29qg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20170804000000 20170721000000 31500 . fSxz+ikGl01EsGT/yLc4BgPWf+KePqx0 TfWiXV1dZjiX2xajzeVp75fv3TsFkgWu zsAYmVTeOdcqWSaYrXarBi8p0tu3HWUs XB1qBLHcW31rfTYI28NsurIPSZDG8ZdD No7LQ32eO9YxzsZv6Wbe6XGkuYLmWevn Spy2sPobBe/UH585XyuZds1U3VLJ11Ys sOk1EUEF4bQswmVRjNX/Snr1PEH9pqDY BJAh29t1VNAyhlEibAztATVJqayLKxGK SCDVmzqIQoafupmcl6SruyhJJx8m6SEa HyknUxV5JC4vDSObwv2INc9nPH9vODF4 dhzHYbW8FA0zON79JuNUtg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170804000000 20170721000000 31500 . 2z1lZl81ufBgiUsfgVnn3524CNxoFA5T 8Wfv8Up/484yxitiEFXCR/AohncE2kRH pWN/9UmuzUALJk10SYcD185lBoqhB5iI NPllRyYNYcnuS4Oq7sp+52tJRxqkMIWX V8v09rjhQzTx24UNNB5uZncOmZA0e9P3 KoeR1wG/Eh0dUESrjiwOHvzfmOffAhy/ ymY4XE5KZEwSxKaUfoUT8VgiKLxnNqwB UiCN15xSCAQBpHCxV1owQFtqYfKzNiML jL/ZjznEQfcvdGvfVAM15nLg6VNEYL8G LxP2CgH5NZpakCFRLD3Bos8F3w6WBuME 3CXUo/gqNlV+EQ4R823SxQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "check and change answer"
+test. 1 IN RRSIG TXT 8 1 1 20170804000000 20170721000000 31500 . dnGQw6mSrNv3mhxWLQ+jFwHQea3PtL2t TOAGEVfyal5Kjpb8aVRWfCcBEdz5JVgh iXHXrZ59dbQA4sQbBrtCZLQl9eSjD/xr sOvMl+/gXMQVMnKBtjmN/kXFliuibTvC +1o9ZplEvWIQUDYZLk1SEZZQNNEnUIBt B/8N8EYrMfGRMkZkyD28PhbrFL65SEJg qyjJlwYsb7wBrElXQp8WoiPe44oBAL/0 3Xs/FkDh/52nHZ/79SW/d8iwHD12my1d 0cEYphlyk0nFs2zI6AnsbGODoVDb3snb 9VV4XJLkYdeo+iTalF5v/0Ept9PAMw8n bz81f7Ww1yDZ5KLcSaQzmg==
+ENTRY_END
+RANGE_END
+
+
+; 2017-07-01T00:00:00
+STEP 20170701000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170701000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170701000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-02T00:00:00
+STEP 20170702000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170702000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170702000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-03T00:00:00
+STEP 20170703000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170703000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170703000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-04T00:00:00
+STEP 20170704000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170704000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170704000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-05T00:00:00
+STEP 20170705000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170705000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170705000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-06T00:00:00
+STEP 20170706000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170706000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170706000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-07T00:00:00
+STEP 20170707000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170707000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170707000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-08T00:00:00
+STEP 20170708000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170708000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170708000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-09T00:00:00
+STEP 20170709000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170709000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170709000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-10T00:00:00
+STEP 20170710000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170710000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170710000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-11T00:00:00
+STEP 20170711000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170711000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170711000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-12T00:00:00
+STEP 20170712000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170712000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170712000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-13T00:00:00
+STEP 20170713000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170713000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170713000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-14T00:00:00
+STEP 20170714000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170714000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170714000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-15T00:00:00
+STEP 20170715000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170715000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170715000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-16T00:00:00
+STEP 20170716000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170716000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170716000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-17T00:00:00
+STEP 20170717000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170717000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170717000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-18T00:00:00
+STEP 20170718000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170718000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170718000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-19T00:00:00
+STEP 20170719000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170719000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170719000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-20T00:00:00
+STEP 20170720000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170720000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170720000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-21T00:00:00
+STEP 20170721000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170721000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170721000099 TIME_PASSES ELAPSE 86400
+
+
+
+SCENARIO_END
+
diff --git a/modules/ta_update/ta_update.unmanagedkey.test.integr/unmanagedkey-present-monotonictime.rpl b/modules/ta_update/ta_update.unmanagedkey.test.integr/unmanagedkey-present-monotonictime.rpl
new file mode 100644
index 0000000..5011210
--- /dev/null
+++ b/modules/ta_update/ta_update.unmanagedkey.test.integr/unmanagedkey-present-monotonictime.rpl
@@ -0,0 +1,757 @@
+stub-addr: 2001:503:ba3e::2:30
+stub-name: rootns.
+trust-anchor: . IN DS 63640 8 2 00EBC5520847A812819359F554C1701C9BDA488A6111BBC4ACC47A32980C1FB8
+val-override-date: 20170701000000
+query-minimization: off
+CONFIG_END
+
+SCENARIO_BEGIN Simulation of successfull RFC 5011 KSK roll-over during 2017
+
+
+RANGE_BEGIN 20170701000000 20170710999999
+ ADDRESS 2001:503:ba3e::2:30
+ ADDRESS 198.41.0.4
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20170715000000 20170701000000 28969 . nzThS2g1c+WqXeYIM3Lkkd8s54Giigvd K+Cmb45GTsybSEDPweCbrX3sIKIRLTee h6PqpL1OO0MJRe5RQRonrnRwUsL4zV2Z Ql6+htnV3o5KkS773Z/nanRKabq3ubIF Ct7JCmMBJ383pcYdFYUJXviDwI6Bp/oW qoYNPiky7OkQ/dcE6SKCIokuQtd3yaIr QZPbry9mhAEIfw3a3VRdB27cSAdpTsXe gkZv/HV0/u153F82hlJgB3rHgmV3IKuL rz721IyKst1JF+nGpg4eg7zBK4ivw0E7 wEt3+3eTznkKSa06Nu86CgK3Xg1SVQcx P1vTzS+e9qKZhrsgqfA0Jg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAaJ74lYmnNx+UIkPCTsfndfoXZ6S MprUoVw6UgEOgBC81w6szA82TssdOAFM iNk5lIZ8uRl6u1KO5sY3JMi/yau4ND4u 30H2IyyIIdIu8ad8dCbxoYXwh3ErrODc 2xXWG/x0mHR27/HEPOXs6U4iweuBivJD 5yWhxDWafrM9MjNvKGefic6B8/qqSdtI nr2WCxafZKHZ0xBBioFi3x5IHxpKj6aw JrXJisNzhNlxCJI90qEw7eUfg9eqPhx2 dTsOE3OghNYncDi6t74f319q0mWaEKAn cip5W8fCZQdVKry9QjDSM8tsqDkjrlkO CAbbrJjlwOzbM5y9EcpvwJTtA7E=
+. 1814400 IN DNSKEY 257 3 8 AwEAAbE+Bu1LTy+xuBMo55R2EJASOaWy KCCJYhVRsx3p6CqCiMWonDOKG91FnOSY jwhao5e8wuzNNHArIxhvsTLkUS5k1Oaq mt2E2Fw8Dkr6Y+H23SpcaNATIwcr8wnS 6oSFYYR+IfB/tynu3HVZ6rFPNiKGr1Q1 Z+HK2hKWSqDigtwi08UVf/Vpq2jrocGg K89G4RSC1gK7MNjc7mDe21mzSjOtQqRC Vl3kKdMbRIPX7y51W7b7k7MBfq5/MpwO 1Pq5nDpClBi2823yPEZQHTV3SlOZkY0B WzJ8ahEOwmr0d+SsoKFSfXDAGhubr5gV S9YTHjun8oocjRDNxEcM0itRCSc=
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170722000000 20170701000000 28969 . WBUz87YLXQOj/cC+6+8qzpBJz/GhSng+ b6TzA0RQRgh4EsgXKnuCiAnU3fAtDvGr 2pQEF2ySgHNB7Ef210y2IMoR00W8zSAh oTOy/Dbb1Xf/PYznEbnBmTdD7wjDXNwd p/rb6xySvWEGC6a154KEuFzMWYB2bSsA ylmhnqmDeC1Z25xcxJCWzphfaMSirpT1 w7KwcNrHraSYY154wbpTD+5X/kmRNTTi wllR0lymoyintdWrA6ofRxQRg0Qwq1kN ANJ03mskJ2/wpE19byde5EQxMbuYxF9c ++HvmtO4+l3qkGRjxOj6BcFol0jL2fDw e2/7whVZpR30Vad8ir3LXg==
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170722000000 20170701000000 63640 . e6NxhVRd+UaOb9A+HTuXr+d/5shed+Qd GLHlVlAmXd04ktEGFy+34tbGt/AfE49i tjVXV8gjxxI1CBNprzrOJFEvdmonRih9 uU3aIb7nIx+EZHLawJz+533ZnpezLKA8 YjqJo4PECsehejXMvqjqSwcmgF/9sVX+ O+RyKRecn92NldNogvTuDgll9XF5kLRu 7/gMvzhpfpbTPz9JeblDrJoMGON+5fGo zYL48k8p4pLoy+Qe5FiP0aLNyTvRCvFM ioXqpehA9sh0PBIMWEatI7sji0Z/4yGa Grni32o+STvVC8Kct4IeNxxfHXhPSam5 hvHqB4P7lELbKj0VZDcBFA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20170715000000 20170701000000 28969 . bsiTkZ+Qbpg53ZIOX2LpeZfoXSgUqqo1 0eK3pxh0G9irczolgFUGaWIf6KDlm8dA Sr9oR86U5IMgjRnghX2XlGBFqS0InYOS TGAV1ZRgiB7/u3YFHPw3qbw3xX/NHBpc qHr8/qnoJUnQrwrhzawpSu+EybhCsHfx 518MRPMxdQuxkC+rHE69PWQgjM7BE55S lQvJd8iASQlWMdcnC8As9VRxki3aUykL llDOTQeQvRRc9abPRFK43SbiUhO7JOpc pF5CTHy+barj1KVyju1TeERrYVe7pR0L pulbvwWCDgmwhuHaPJmy/A53e+SkQTkj buoCA8biSmFwIB89psFZ0Q==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20170715000000 20170701000000 28969 . YNPFwsjeDerMI+ONb0ksp5oZ9V7zuMts e1sTb5RxffdK8z4MqF1YBw0vfhJ03AYK U2OyS9p1m+41T1IS3+9Unk1grgyUCajP Pek9eAK0FMFZyuQMrQQMmOcbboKwa6G0 x6UJTIIy+evnMrLnbfK4EHzzbmdmmcrH r2sD29RjbH03U7E5Ie6gohgfaV1cvirz dyue4YIcUeQ95P6yqihMK3zJyeBZpcq9 fBm8TKg4+chtkjT/Xp+GuVH53gCoGMQF Muf5CpWnd6CTJ14CHDcoTR1WvZx0ISUM uVCjdnCq99q173yeXYzptgbESXcHgVh6 5wQbuemJi7yqMdk4742eMg==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170715000000 20170701000000 28969 . V4mHW8mz6d1wD7CXTLW1Np4xUrFJ5F3k MeVbke0Jyl9ciSthVZc6Qj02YbESegVI p7WZfi8I5w646MjS6d04CC2XPLv8nyAU SLwUvKgYCdwE6swEcb4QgpRX1Lv0WxTB 5taQWrJDwQ/G0ifnOEWt3iQPbXAn2UTg 0pnde9arz9RU4V430mdMiWc84xNtRvP6 Ncgu4GnT65us1QGKWetKEOoJPU4RQztE OkJu/Abw8CPLpfirckMEdDprNqO8NELF nj34L/dxS4yEb6vYp6sPjL6y9k0xdMLG 1g0RDWzXeCSKPb847WeiBj4D7I6i3XAC Dkj/SiWQ+IVjiDHD66rl0Q==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20170715000000 20170701000000 28969 . nzThS2g1c+WqXeYIM3Lkkd8s54Giigvd K+Cmb45GTsybSEDPweCbrX3sIKIRLTee h6PqpL1OO0MJRe5RQRonrnRwUsL4zV2Z Ql6+htnV3o5KkS773Z/nanRKabq3ubIF Ct7JCmMBJ383pcYdFYUJXviDwI6Bp/oW qoYNPiky7OkQ/dcE6SKCIokuQtd3yaIr QZPbry9mhAEIfw3a3VRdB27cSAdpTsXe gkZv/HV0/u153F82hlJgB3rHgmV3IKuL rz721IyKst1JF+nGpg4eg7zBK4ivw0E7 wEt3+3eTznkKSa06Nu86CgK3Xg1SVQcx P1vTzS+e9qKZhrsgqfA0Jg==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20170715000000 20170701000000 28969 . flD/0sHGiEdO31gKLXt9gt0HNSXXza0q 4i39YuCkJ0nKhcToFVPk6lo6XKLe34QW CqdR5rLlOjlul3tUrEde7flEhCyLf1du 6cF/AxBrZ1qRNrUOG2seSjFmBNxFD9p4 pTG/4lwRzr6p/TbaW1YyXWm65qi+3TGx 8DlD/HkcIU/Nc8zhqORcN+AhAl5Zp0hN GVTAFAMzHBPkJ+Nuy7iUYQPw5S2KWm9C ozbH7skiFb1eQgJq6oV150sGIqcrQhxI xvHKjlQLaLU8V+QcmNhLVz1KXdPiLe37 LgM4/5GuvoywISknr4lucrBxFJym6bA6 3zbaTiqBbKLo8fAuXG06kQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20170715000000 20170701000000 28969 . YNPFwsjeDerMI+ONb0ksp5oZ9V7zuMts e1sTb5RxffdK8z4MqF1YBw0vfhJ03AYK U2OyS9p1m+41T1IS3+9Unk1grgyUCajP Pek9eAK0FMFZyuQMrQQMmOcbboKwa6G0 x6UJTIIy+evnMrLnbfK4EHzzbmdmmcrH r2sD29RjbH03U7E5Ie6gohgfaV1cvirz dyue4YIcUeQ95P6yqihMK3zJyeBZpcq9 fBm8TKg4+chtkjT/Xp+GuVH53gCoGMQF Muf5CpWnd6CTJ14CHDcoTR1WvZx0ISUM uVCjdnCq99q173yeXYzptgbESXcHgVh6 5wQbuemJi7yqMdk4742eMg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170715000000 20170701000000 28969 . V4mHW8mz6d1wD7CXTLW1Np4xUrFJ5F3k MeVbke0Jyl9ciSthVZc6Qj02YbESegVI p7WZfi8I5w646MjS6d04CC2XPLv8nyAU SLwUvKgYCdwE6swEcb4QgpRX1Lv0WxTB 5taQWrJDwQ/G0ifnOEWt3iQPbXAn2UTg 0pnde9arz9RU4V430mdMiWc84xNtRvP6 Ncgu4GnT65us1QGKWetKEOoJPU4RQztE OkJu/Abw8CPLpfirckMEdDprNqO8NELF nj34L/dxS4yEb6vYp6sPjL6y9k0xdMLG 1g0RDWzXeCSKPb847WeiBj4D7I6i3XAC Dkj/SiWQ+IVjiDHD66rl0Q==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20170715000000 20170701000000 28969 . dp1L09l+y0OTOTFCnypmZ5taInflPNAU Iuh4bVn8zQoKC4O4LBoXhE5Udt+y9ul/ pyMP1JHtcJKD8AWjABQKz7FQdnOzEap+ ioUA+qZicnnW+MUaYbtN9jgKqnOfanxM PXSSjzLDfFEEZip9Ii08XKkc6zsmCJ6N Gbmq3fOhOXy01cQFoKYC3WxW853yB2s+ vLhirvAusQxfnpHW9ghN0Q4Qbb+OMccE PVltuWzVypRXXUj92EJzCQ8CCyshvgGM gxfpI+T+K5h9xAn8PGFBmhanqnSI3FKI LzAqQvxfZC7vsEQq3/6sSO1BKVLZjXFJ G4C1BaIdtyvd15UIfykePw==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20170711000000 20170720999999
+ ADDRESS 2001:503:ba3e::2:30
+ ADDRESS 198.41.0.4
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20170725000000 20170711000000 28969 . fiv9MSFJla1quIPpZIp+qWqzS1VylSH2 FkF0swo2rbdpidqHPfJnZWJR25+XMch8 i6URA5mRS0Ce7imMxNJVDFyDV3ahusSa LwI6mPnfzgBURkv3MjW38/9LnAVfaun/ NU7WOypPskk4QXfoyJC0Po96LoaU5Nbh iN8IFnipmLQ6LV3nPx3hItGBxL70M2uN FvwD31BInnjG2R+jXDvSzLEeFmoqT6af QXWxNrcKYrJUNrS9x4Sop2XbrYgCvUe1 +UXb5Z0BFgeDeBFFxSSeUogW8SEjCJtS DeUl4RSVQyIqbaeDQ06YPCwY40Oe2buV wSCvZoWeU8p8yOkzwRoR6A==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAaJ74lYmnNx+UIkPCTsfndfoXZ6S MprUoVw6UgEOgBC81w6szA82TssdOAFM iNk5lIZ8uRl6u1KO5sY3JMi/yau4ND4u 30H2IyyIIdIu8ad8dCbxoYXwh3ErrODc 2xXWG/x0mHR27/HEPOXs6U4iweuBivJD 5yWhxDWafrM9MjNvKGefic6B8/qqSdtI nr2WCxafZKHZ0xBBioFi3x5IHxpKj6aw JrXJisNzhNlxCJI90qEw7eUfg9eqPhx2 dTsOE3OghNYncDi6t74f319q0mWaEKAn cip5W8fCZQdVKry9QjDSM8tsqDkjrlkO CAbbrJjlwOzbM5y9EcpvwJTtA7E=
+. 1814400 IN DNSKEY 257 3 8 AwEAAbE+Bu1LTy+xuBMo55R2EJASOaWy KCCJYhVRsx3p6CqCiMWonDOKG91FnOSY jwhao5e8wuzNNHArIxhvsTLkUS5k1Oaq mt2E2Fw8Dkr6Y+H23SpcaNATIwcr8wnS 6oSFYYR+IfB/tynu3HVZ6rFPNiKGr1Q1 Z+HK2hKWSqDigtwi08UVf/Vpq2jrocGg K89G4RSC1gK7MNjc7mDe21mzSjOtQqRC Vl3kKdMbRIPX7y51W7b7k7MBfq5/MpwO 1Pq5nDpClBi2823yPEZQHTV3SlOZkY0B WzJ8ahEOwmr0d+SsoKFSfXDAGhubr5gV S9YTHjun8oocjRDNxEcM0itRCSc=
+. 1814400 IN DNSKEY 257 3 8 AwEAAduG/aGjlqV4zHxSHn/VNbEp+ns+ KEHgGj/4/MLCcaTiMXqQuZFhsYbRGB2K fDduGNxcf1ETC4iuU0Mj8++3ofhALnAz irQsKOxWpWCvwWJC6qlp80U0GYIkjZA8 7XEzSAx6jZUPh/9xMxrmogIxwJ2KP7KH X0aF2jh76+HA5GmEBG+7dq1Z0v1l22aY gvyK/uUntu1vaVyQqyD8IhaAkl+mI2kr 2N0Cp4UcKutuRbaveUP/LdTpJoHmN9CR wc3LkvbBYB5F7VL8hgnhKFIcbQHhYoTQ YCQzgBA5OxcfWFWBfQGtpaSmv3V+aQjI JhEbWLDmN2o2kwl89AN3DGtEtsE=
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170801000000 20170711000000 28969 . g/ODtn/IzUHbBK2B8dS5D3Q+Ee/8msx6 4ONLHXf2SolIxfiTm9Pl2Kqnm3SRPsid rwL2LGkdMMMkE7faX2cE0PW8/b9y4VhP 4V+piqiMJByUM9qU4ZdMZ2BvMlLhfmTC 0QI36vnIfIwDddBJ86xo/ee3PX5je9Vw xardvEpvpLDe5oWf4ThoT8NW1nJ/P5fg /cff0U8qtLXcZkHZfTNWvx86t0bOg1qv uWOc4aiMne1FePp9QCmVs7D2s0gvuK8T P6tbx++Tz+0setDOdapSo0oe8PFvPySg GxRQ9L2CtVPv8YmKheojXoyKJ50KzQPY yWXl5FAXLfx1PKfPgq+X/A==
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170801000000 20170711000000 63640 . jVqcr43tfrNMVgoentuUlqxZ9lr2SDzo xEgZtnMtNkK7dr2MjI4OhZvzSkepIAI2 GppyU5auUqJwrKXwk9VOfQy2a2yBzoqQ XC4h3BRuWlmw00zg5gLgys3M41um9sx/ QSZ51ScaZxVw3cy4Zf04LYx5RARcp3fM iRVVVLPWYVtd5ATbUcp59aCUa8EvQE7Q t0bkzBDd5dBOnrANPFnhHYhPPZICuKqd ll7hTN1eQNqGmlKNb5fYeAZiD5+qg2vf JMo1XpY656HLNowVsAS5TQ+kP3+genb7 AOM1iIZEkCbiByrjeXdJfPARlkCODJ5h PcBkN9bd6JB5EhzOSIK/wQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20170725000000 20170711000000 28969 . aVcR90IFl5ogQYSg60KeNHRviuSC76UZ DsLKDuZCljZIEI5S/qu5hAVohsW2uHJ2 1hMJHQKuOyWJO+3GtzLzqSWxZSKxV0/N aKWhQEbB7+gEm50ZLM8Mw6875vkpOUwF 3K2tzFH4MVBiJowL6whqeOadpmDvDbgy VFcFHw+2+MLVEOG0vruj5jQD1wTelT7k GE3CdwLt6m5TkLt89sJkojRpbK2Jt3zw X+/j0vzJbwQJSbWWxftSdKY+vmHJCwpn 0hT6Ax4jx2e2w6/vkMI8izaglsOIaCBE d1rzXR4DdEhqaCPpLqYLZaYBrNhdZg4c hvpyn5sdugZcDd2vp55iJw==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20170725000000 20170711000000 28969 . e/85gZqEaHv+Zn+I2FJaUkZHHDzhnzuk iOAfP2EiiTuzPR9B18PWpIJxzFp38+tX Z7Ny86yj68Zrk3aBBDMOgynIMdNd/Xf7 lsYQzAutq/BFRlxtPmPoIpKSSSd+F6ka MVTVxISjaLWADjYlKYYLMjcX7UYFYdXS 4laxdjtYPLCSKDHBBYFyaOxfV6TfVR+K t0JQUrPcJTTRVAORkQQgXkd1Qsah+abc RN0AedeMG3jQEaSqJL5SSHRqjrqHWx1R czveXdjs+2wZMwq0S8mkTQXTm9H9Zi+c GR2841N26fnEI0qCSHUru2AngM+k0HJs Qz2wrV/VENnRJjxJv/VGhw==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170725000000 20170711000000 28969 . N0Lwwyu99CFaAtoj5SVsT4k5I/DW/qd9 WDzeYx9dXL9Ra15jGCkoyvEl8+mdd1EE Knj7Ri8m4L13mI4B/8BKsssMUGRu4k3c B9ZcIkVWs+xIOu2YLnrzz/s4JQbUxzDk 5Bu5Rhi7sF1xIfbl1T6pvw5exGkPYjMW MYVXlq/vfRWqgoaYdQ5MIukT52a7OhUn CGtoqzZoSnYkmKAwAv9rlSDkTnkLXWtt VTNfwZxAgWsEJa6GacsauPWEpNElXIvh hG9SX/s8YPDhcLg/tTi9ZCZm5VDkXzAl zsGHHEQ+HT/qezqCMMf5wEyqnhDPv+XK YOO6EERSCTpOaHvOo2Z8wQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20170725000000 20170711000000 28969 . fiv9MSFJla1quIPpZIp+qWqzS1VylSH2 FkF0swo2rbdpidqHPfJnZWJR25+XMch8 i6URA5mRS0Ce7imMxNJVDFyDV3ahusSa LwI6mPnfzgBURkv3MjW38/9LnAVfaun/ NU7WOypPskk4QXfoyJC0Po96LoaU5Nbh iN8IFnipmLQ6LV3nPx3hItGBxL70M2uN FvwD31BInnjG2R+jXDvSzLEeFmoqT6af QXWxNrcKYrJUNrS9x4Sop2XbrYgCvUe1 +UXb5Z0BFgeDeBFFxSSeUogW8SEjCJtS DeUl4RSVQyIqbaeDQ06YPCwY40Oe2buV wSCvZoWeU8p8yOkzwRoR6A==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20170725000000 20170711000000 28969 . hJ4FfZ1c4tXEx3MhS0iz/Ypu6Qincctu Dzc9PHqhMUCAKumNBTpbiQpqjnZEhi3m a9iEx11ysLFUp+5REWgSCDdSHkL1reVX MoE0/Uvivz04VBbj9YM1RIa24hsmO9BH b1j7rnRZGLz5kGllKPuD2V360cV8MPc/ HGV7xgB/j/T9MBk555Hhu8oqiaj/YXzC 4MT4UM5TObPd7+NstwocO5x0WdpbqhM4 D1eXxq7TRS42EKGRZ3uFjn+szrtkcQ1l fcxkAs+VcCyGBeZ51cvFZmNwQwjgcIZt CP5OwGvWdJRi9JPb5jdTImNVFe6xOV6H h8I4MQl+yBEw+62LKWz4YQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20170725000000 20170711000000 28969 . e/85gZqEaHv+Zn+I2FJaUkZHHDzhnzuk iOAfP2EiiTuzPR9B18PWpIJxzFp38+tX Z7Ny86yj68Zrk3aBBDMOgynIMdNd/Xf7 lsYQzAutq/BFRlxtPmPoIpKSSSd+F6ka MVTVxISjaLWADjYlKYYLMjcX7UYFYdXS 4laxdjtYPLCSKDHBBYFyaOxfV6TfVR+K t0JQUrPcJTTRVAORkQQgXkd1Qsah+abc RN0AedeMG3jQEaSqJL5SSHRqjrqHWx1R czveXdjs+2wZMwq0S8mkTQXTm9H9Zi+c GR2841N26fnEI0qCSHUru2AngM+k0HJs Qz2wrV/VENnRJjxJv/VGhw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170725000000 20170711000000 28969 . N0Lwwyu99CFaAtoj5SVsT4k5I/DW/qd9 WDzeYx9dXL9Ra15jGCkoyvEl8+mdd1EE Knj7Ri8m4L13mI4B/8BKsssMUGRu4k3c B9ZcIkVWs+xIOu2YLnrzz/s4JQbUxzDk 5Bu5Rhi7sF1xIfbl1T6pvw5exGkPYjMW MYVXlq/vfRWqgoaYdQ5MIukT52a7OhUn CGtoqzZoSnYkmKAwAv9rlSDkTnkLXWtt VTNfwZxAgWsEJa6GacsauPWEpNElXIvh hG9SX/s8YPDhcLg/tTi9ZCZm5VDkXzAl zsGHHEQ+HT/qezqCMMf5wEyqnhDPv+XK YOO6EERSCTpOaHvOo2Z8wQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20170725000000 20170711000000 28969 . Kt5QnrqvSHi9bp16JY+zInP+jvKrqCks 7TrTskXzZftyHL5bnYVTXTJekoBE3S0j cQaU24/xbDfXZ+vbnaNbfL5lQtMV7cyC Hq5ydrkfmDUDK83J++RR3zGbSEg0vhiv Mign05MHxpwIAaQLOfABc8TVHNC/uTar el66xI3MPAm1dovqV8rvKXhsZn+c+2ec izJ06J3OnwYB7Qtvv79J7uPKp9VeuY2c gXrPIR73laEHlV0pwGZjWk1Pj6T9w2KX tzcALzFsGPlvf7WwQlOMfu0PVvp1mg09 LLyosDdjDVuMdFZdoie9FAXDia3LNDVk VcWhf53KK2kDA+2MFw44ow==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20170721000000 99999999999999
+ ADDRESS 2001:503:ba3e::2:30
+ ADDRESS 198.41.0.4
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20170804000000 20170721000000 28969 . RF31T45DXJHLnVI6wENwbT6Enc+9RZt2 ydueew94jnGTB55BJT9Mq2rWkzkP/ymR RzZjixoBsoRgCFlfs865Z+JJtR7nk23s Cl02erMw6lra4IaD8q4M/sH7vh7AAo/c /GVqKyxcCZiIwk/A3J1qpAAvbapE/jwT seg04WM7XEsqp58BuNluff6SavIaFB/Y 70sGNMm+jbBnk+W6HOJw6bB1GOyLE1Gd Ae+YMSbEA2H99WAZo7rlKO43CQY8sbzY 3pKCrY6kXNoBQSlpqWxjU7UIjYdU/8F6 RrBJzq1aIKBEjAh2zEqqpb1NyxfMoJku 2I6eERUs/J9MNc+gn3pwgw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAaJ74lYmnNx+UIkPCTsfndfoXZ6S MprUoVw6UgEOgBC81w6szA82TssdOAFM iNk5lIZ8uRl6u1KO5sY3JMi/yau4ND4u 30H2IyyIIdIu8ad8dCbxoYXwh3ErrODc 2xXWG/x0mHR27/HEPOXs6U4iweuBivJD 5yWhxDWafrM9MjNvKGefic6B8/qqSdtI nr2WCxafZKHZ0xBBioFi3x5IHxpKj6aw JrXJisNzhNlxCJI90qEw7eUfg9eqPhx2 dTsOE3OghNYncDi6t74f319q0mWaEKAn cip5W8fCZQdVKry9QjDSM8tsqDkjrlkO CAbbrJjlwOzbM5y9EcpvwJTtA7E=
+. 1814400 IN DNSKEY 257 3 8 AwEAAbE+Bu1LTy+xuBMo55R2EJASOaWy KCCJYhVRsx3p6CqCiMWonDOKG91FnOSY jwhao5e8wuzNNHArIxhvsTLkUS5k1Oaq mt2E2Fw8Dkr6Y+H23SpcaNATIwcr8wnS 6oSFYYR+IfB/tynu3HVZ6rFPNiKGr1Q1 Z+HK2hKWSqDigtwi08UVf/Vpq2jrocGg K89G4RSC1gK7MNjc7mDe21mzSjOtQqRC Vl3kKdMbRIPX7y51W7b7k7MBfq5/MpwO 1Pq5nDpClBi2823yPEZQHTV3SlOZkY0B WzJ8ahEOwmr0d+SsoKFSfXDAGhubr5gV S9YTHjun8oocjRDNxEcM0itRCSc=
+. 1814400 IN DNSKEY 257 3 8 AwEAAduG/aGjlqV4zHxSHn/VNbEp+ns+ KEHgGj/4/MLCcaTiMXqQuZFhsYbRGB2K fDduGNxcf1ETC4iuU0Mj8++3ofhALnAz irQsKOxWpWCvwWJC6qlp80U0GYIkjZA8 7XEzSAx6jZUPh/9xMxrmogIxwJ2KP7KH X0aF2jh76+HA5GmEBG+7dq1Z0v1l22aY gvyK/uUntu1vaVyQqyD8IhaAkl+mI2kr 2N0Cp4UcKutuRbaveUP/LdTpJoHmN9CR wc3LkvbBYB5F7VL8hgnhKFIcbQHhYoTQ YCQzgBA5OxcfWFWBfQGtpaSmv3V+aQjI JhEbWLDmN2o2kwl89AN3DGtEtsE=
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170811000000 20170721000000 28969 . XWhkgZWb58eV5z5eoGWQwq65HbwA5ozF b9BPTQEwph8IAgvjoK+5+fW8Krf8rxqG nZDu7qFHN/T0s4wux+8+ey2HSfRUXj8Q 4xNACKWavLaIGundNiYleVjzmeOkbv5S vOcINlbhuLsm3J0AmXCe4/QMfs8yJ6+k VCHCAgUwykq9CoOQlJW3A/C7Ky5Ztx5W ujuc+7Ou91SwT880xBfIBD17U4PXF3zE Xmdd0/D+vaEBSmT526Ons+fEXP8W1TK0 1xBJGaGT/N2pqaOQOnj7N6Y8gjfmnJso 4J5wZ91B7f5Flc7Qzd4FxAlHkbKroSl8 yS6eaYJzRGZc5+zHActPxQ==
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170811000000 20170721000000 63640 . fuaYzy7riEOS0vwe0ZaR5hPMD8S5oGeH PaZZ2jCnWVDuiFvPObYVuuB9RfWhyyLB +LOQ0xkoV5LRm8mmbqoZWniy4HS/4+oc 3TeIenMJLdP9FqLdgz8Pk3aO4O0o2FgT 4P8jlQ41R1TUct3vBtcjqzzKQXqOqA9+ y+iSPspWs3r305u49YyknOin8OAE3Zoh J2l+m/fVvW3pQ0jKCkD0CMg5k0nbORs5 aK1T5UAuKPUTwZoL9i+Bov89rQWWUp0B POzdBFHCM6nMlo1Wtu4RQHSL0GmLDsSS 1SZoOdbCsrX1DWCpq2mg9yCy/mnsM5sE +oSy/iFy4F02Ggk11R4gXQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20170804000000 20170721000000 28969 . A5hMjKgZIhBWemZZUirkziI/ObpsHmlT +2zotPOO3Xk+8niKkWNBo6d8JCa+ofyd bhQb+3486j8YgKgjzz2YyjJtGemRJrLm 1frRV6lxx3lRYZCmNyS3ZUnN8vJaIREV WXr8f8HtGov6Pu9lIXikLxIFo9g2dMK3 jUYXbA6HTJBh5Eki3xBW27yGFlq0hGDa E2fV2swD5C8vQmo1KK2yDzA92G87eCXt pxrz7b4BKPXi5kadBapudB7r9htkaI8S 5ORTgXpmAcYGDk4sVUg/f3hp/fyU/CMc 9KVZPdLjqGUL+FnlBee+L+wR0HSCc13L j4RK67DtfsGMpeR80LlcZw==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20170804000000 20170721000000 28969 . QZhev/bmwYaANmOIgiKUZbhb4+6cf0pK AYWChRC+oFv2KSlI2qkWjdu9D36Cxwph 4q0UeKsz1BgB+bk6iA+qCdZdv550ssvw LALjZ59bhnD0ts5hfKttpSVRrRLocL8L VOKsh89+vcFggOtNBNxR0uvQZaDwHAqB 5qle9YjqICpo66kXaHtDVB0rptYwnRoP je5CtgGVKrTgxFnNvuQhqq+Jex1DetFU CQcvlObPV57LxGIaWVsHspO0y1u+U5EU rkICR/j3kCX+ugdTfRYhGJ+MiDdhqj1v KaXmVuAUliybx9ElQA9AjjLy9SdmJriq rr86biqSr3tpfAB4B4RfNg==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170804000000 20170721000000 28969 . ceLKW/BKyqC5qryqZWXFaFVMRJOu7Lrh dKqcqh7zHevV5ZGF+RzGtGX0XeOahzoo 3W+tM4r7oP7yduPetBGQnnVb8WiuEs2B YUx7LwFdR+I9hSIHVAmB9rIPVm9QHAx0 KCYEIu5Trv7sxWGgetxdXQwMeAY+xHhG YdXZQ1vgxM7bG4w6Ei9+pKi3iw6oH7pX 91uldh9Xgi4ajFcYQ+/N7azjddzv6jKB cTro1PQTiX80vUnAZQxTRQAMvKfWkWZM 8mjna00RSnvs2T7EPS1wrL7sK4os9dU6 1B5SoQhTrJRmzF/Bc1kB1lXhTW8TrPnV UMeoJAN9VFEfRC6fqOq4iQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20170804000000 20170721000000 28969 . RF31T45DXJHLnVI6wENwbT6Enc+9RZt2 ydueew94jnGTB55BJT9Mq2rWkzkP/ymR RzZjixoBsoRgCFlfs865Z+JJtR7nk23s Cl02erMw6lra4IaD8q4M/sH7vh7AAo/c /GVqKyxcCZiIwk/A3J1qpAAvbapE/jwT seg04WM7XEsqp58BuNluff6SavIaFB/Y 70sGNMm+jbBnk+W6HOJw6bB1GOyLE1Gd Ae+YMSbEA2H99WAZo7rlKO43CQY8sbzY 3pKCrY6kXNoBQSlpqWxjU7UIjYdU/8F6 RrBJzq1aIKBEjAh2zEqqpb1NyxfMoJku 2I6eERUs/J9MNc+gn3pwgw==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20170804000000 20170721000000 28969 . ErIpaMwf+buKKDCcMvwoqji0YvvSacdL M4zSZKjiLPhGhaDmcWTLECes4En511Pt m8ft/F+ghQPobhH55VbBFFPLpFqVXg7/ JO/EoUYXDKSmjOxSLre5UVzCeeyKq2wf GmTLZUyhRJQbuQnzv29IIhr5pGUcFebU d/B4blebChkwSH+/Pb8LIfBRiMEoFXiV sHu75Pl9pKgAe76fUUdstB/7vT4SGXHa hHvduxGk3qSBqfi8h+/mnQzfk1H5UiUs U3Uvgq9JuWVBioSIHh4pF2RUMkk4aXCl 02H9iBZdIU3PjQu0O+CJmjFCoFslxUrK /6IQMZc9Ram1zUd/HfKbUw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20170804000000 20170721000000 28969 . QZhev/bmwYaANmOIgiKUZbhb4+6cf0pK AYWChRC+oFv2KSlI2qkWjdu9D36Cxwph 4q0UeKsz1BgB+bk6iA+qCdZdv550ssvw LALjZ59bhnD0ts5hfKttpSVRrRLocL8L VOKsh89+vcFggOtNBNxR0uvQZaDwHAqB 5qle9YjqICpo66kXaHtDVB0rptYwnRoP je5CtgGVKrTgxFnNvuQhqq+Jex1DetFU CQcvlObPV57LxGIaWVsHspO0y1u+U5EU rkICR/j3kCX+ugdTfRYhGJ+MiDdhqj1v KaXmVuAUliybx9ElQA9AjjLy9SdmJriq rr86biqSr3tpfAB4B4RfNg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170804000000 20170721000000 28969 . ceLKW/BKyqC5qryqZWXFaFVMRJOu7Lrh dKqcqh7zHevV5ZGF+RzGtGX0XeOahzoo 3W+tM4r7oP7yduPetBGQnnVb8WiuEs2B YUx7LwFdR+I9hSIHVAmB9rIPVm9QHAx0 KCYEIu5Trv7sxWGgetxdXQwMeAY+xHhG YdXZQ1vgxM7bG4w6Ei9+pKi3iw6oH7pX 91uldh9Xgi4ajFcYQ+/N7azjddzv6jKB cTro1PQTiX80vUnAZQxTRQAMvKfWkWZM 8mjna00RSnvs2T7EPS1wrL7sK4os9dU6 1B5SoQhTrJRmzF/Bc1kB1lXhTW8TrPnV UMeoJAN9VFEfRC6fqOq4iQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "check and change answer"
+test. 1 IN RRSIG TXT 8 1 1 20170804000000 20170721000000 28969 . BKc79/iitLm4EWqaIpRy49O0VosanODV w4YR0tVoCQLNKePEVnzBQdPxsBpRV4nk 38TRSqp7XwZJd8gPskXtrfrkO5Pz28M1 8cA6B/uExOBQYCFVGjv7avo07p0EaxGD CjX0yoNbA0Z3SQHhgsUYC4eMiPsOcfos UtnNkZHipA6/QKUD1fqo13Y69gcl8glr Cb/rQn+IYo7GqrRrESUkQEFcwlWd2sii Z06ZchNxTspNL2irm2mqpsmMK/osEkyv LG9dauX83ZO6WaQlZmDspaHEFbS9HhlI OVTPG7mUTlTROQFG1zTVuGW2gu6kY6ms LFwpurPHw0bVeRJ2JLFifQ==
+ENTRY_END
+RANGE_END
+
+
+; 2017-07-01T00:00:00
+STEP 20170701000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170701000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170701000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-02T00:00:00
+STEP 20170702000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170702000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170702000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-03T00:00:00
+STEP 20170703000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170703000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170703000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-04T00:00:00
+STEP 20170704000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170704000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170704000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-05T00:00:00
+STEP 20170705000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170705000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170705000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-06T00:00:00
+STEP 20170706000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170706000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170706000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-07T00:00:00
+STEP 20170707000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170707000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170707000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-08T00:00:00
+STEP 20170708000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170708000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170708000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-09T00:00:00
+STEP 20170709000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170709000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170709000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-10T00:00:00
+STEP 20170710000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170710000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170710000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-11T00:00:00
+STEP 20170711000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170711000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170711000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-12T00:00:00
+STEP 20170712000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170712000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170712000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-13T00:00:00
+STEP 20170713000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170713000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170713000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-14T00:00:00
+STEP 20170714000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170714000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170714000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-15T00:00:00
+STEP 20170715000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170715000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170715000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-16T00:00:00
+STEP 20170716000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170716000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170716000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-17T00:00:00
+STEP 20170717000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170717000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170717000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-18T00:00:00
+STEP 20170718000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170718000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170718000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-19T00:00:00
+STEP 20170719000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170719000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170719000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-20T00:00:00
+STEP 20170720000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170720000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170720000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-21T00:00:00
+STEP 20170721000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170721000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170721000099 TIME_PASSES ELAPSE 86400
+
+
+
+SCENARIO_END
+
diff --git a/modules/ta_update/ta_update.unmanagedkey.test.integr/unmanagedkey-revoke-monotonictime.rpl b/modules/ta_update/ta_update.unmanagedkey.test.integr/unmanagedkey-revoke-monotonictime.rpl
new file mode 100644
index 0000000..c0949f0
--- /dev/null
+++ b/modules/ta_update/ta_update.unmanagedkey.test.integr/unmanagedkey-revoke-monotonictime.rpl
@@ -0,0 +1,762 @@
+stub-addr: 2001:503:ba3e::2:30
+stub-name: rootns.
+trust-anchor: . IN DS 5191 8 2 78DE555142AECCBFE1F4F24A9053F7A3C8BAAB2891DBB80D0CDD29534A44C3AA
+trust-anchor: . IN DS 24784 8 2 5448342C83F1CCB31F966A835897DF1484B12074AB535B2CB84CFD8E2E792B28
+val-override-date: 20170701000000
+query-minimization: off
+CONFIG_END
+
+SCENARIO_BEGIN Simulation of successfull RFC 5011 KSK roll-over during 2017
+
+
+RANGE_BEGIN 20170701000000 20170710999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20170715000000 20170701000000 50124 . mlDK2ln5t2Ql7/neef2Pzz1URLXCV5ml 0QYXbt2kzrgeKqoHRXMGc6nxqYv5Ttpn TtY82BhNlYuTzubWrrg+SS7z+vSAwUM2 mXG16Y0Z3PoKc/25dmYElwcaG1h10elI unPy1GKGoEkUlEtT/6nHQdkhvyUL5QcX AwjljLIxsY7BnzhmQEsz0ywMrTmg85vX H0BOK0IFBKxLBncx10wvJGApaVzaxdWc 0H/+fnF1r/rg2NvRFuIXlz4vrl9ExNMa ZmXR4PLlZTHO3FaH9yF18IHPCY/bi8L9 NjfW9pj+Fg4LR2kPj5WCkrCR9RKhdUAO xJKLSBuSi0CL1OyLWg9QMA==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAcHrmeeZUfVAS5KzAL2mHyjpdS3y 5Du2ScdXa1nunnaA35ARZR6BbwuQdIik CC1LH8D7d/AlDW7SlkHWeWBK7eMxO0Ps E2u9jlN2yy+9OQwo1kybg4cBDwbgHmu8 ppX7U3S5SpP9XZIJhYRYqNube/zkoDSa uLaeghU13CCuIwdbJrPNOC6Zvn0op3WL ryTVMnMUzgld6WsiTWZDlaMy5IJdhg5R X2wa0UCJjr1M3kguTHftYExgUKbQytx2 +i1mq+vSKqcS9Bxo1vFTWMtFV2FFpV8T Fv4dL1aC1Aq8e9CiAIEfCVVy8Dw+vYpO VDrd+1qkNorMNetwEly9EgkWgB0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAZxJIBnv0pOId/Ukgr0pwVLXjiBR RyV0kc76BssLTIFPWnEWNLv7+3JRvcAm tpHzDvpCoX92taEYVP3pgF3WUNA/LOJR iYIE8taQ0j0TpbdiizFtgFCc+zE5TZ6z /Ru1Eq6VXgdpHzpPtTep0+gB1Qz6HQNT Wd0v2/XQmKWkh3KnJuDSrBjQy50ax9zX 1Sj8Syv+oA0iREf886Bb3e1nBaWrw9RE xovh2F68eE5hxwBcipwNyf7iEN8Um8Tj KCXWeEnTM+Giip0F+JgbapEKr8Dk5YYv nRBLwPZiekL35AbGA9/8PfCwtAIBAj3A CVHhgmlPbLzPGRyawTtIe9D/6uc=
+. 1814400 IN DNSKEY 257 3 8 AwEAAa7l/x0mKre1XGN10bhnsSuo+Lc7 wFv6ksfiDz/kLgbR1TQY37ntCm3akRr/ hp3CE1YUnAGlk9B5lZYEeT0hoD2BkD7E P9DpOxUqKtQeYhe34pO/ygT+dFYSaMy/ 0OStoYLphzbIfElrpg+gvv+CkFKQzO5W rAL1tWu037erXbhSjko7AokpjfEWyT8H o7qGHiW1vWgzWGtWIgXOdydUJYqQNaOY cddJNV23fod6+KrKE245JRv+KabqnLJv 3+D6g7NDh+D5uLiX4vcTsLoB3LiAruQA r+ickjYFsVvfJlJ3m9O0/st9UsXu+v0e 6Esl5DmWWYo63T31sgmSEOjUSBE=
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170722000000 20170701000000 5191 . aIPJnGM1RKAVdVGU1I+3cgs7eSa+mYMG iIzrG9sD98id0zCZ+ekUOYjw6UfWYlBI YT7ebhDIdr0bXwCuiTcvuNZ5ps3EzreW bh6udtTBumshkcxEVFFbzgdUw08L9xoq 9aXDDz5+wS9qbxizaoLYb3DxdDKALKzN VQ3ToLe+V51obIULXYAJbhvUSNRqsATW Rj+Au9j+wiKWgnHl3el+/iwmomNmYvNE USi4+2uHIMA3g47E4VeAGskVgDAskgNH rSeVLoNS43r77UCmpD6WT3P7Kc5BNWY5 RNUX1a17GCnLFamcJlmEnfvxKUC+qTkg cXPe+bGH8W29f9vJFheicw==
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170722000000 20170701000000 24784 . GI/kjTcDyKgcLW98ySEPrNk0kRHR+F4u HyNmPGUq19xUdr3uBKrBUCsnSxb93QRo wb9z7b73FwloyQiXOR9Q9M2MiO58+RDG eEC+G/+67abfItVkBf/ZglcPu+LTyg9F 2y0YzcQ5zH1JkmG/I7d7NGN7Wq4rEWWJ FHAOrIruOl1mNkGnRXDY/vytR4ntiWDk NNpEof6Qfzg8fXP5JegezNwUatwbrFwO xqsM2pITtIS3Kl/H4L7iajyxeQZCqX+R iCZ/O9FIn3Y+OX6JkvvMXGB+0DatTGPJ iE0AHWWPFa1V4SeJz8WKPB1UorvOqM61 LR1GlPWlQGutZhbTlUFLyA==
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170722000000 20170701000000 50124 . tQqe8/mF2f7HIXlXe77EdgRoKu7KqdjD PR/ELk9i/le8GLy/eDI6nlwAfiWKcgqN nFey9UzT/kWk3L8umb3c8aazpuQYJAwi dsZNWlx+yc1dEw6lS+6WqTSiEEzXhQh3 wvOPA58rWflatBYuaAx2scQ/OhPtf0me 64G4O/LIpZO5yLPs3RwBnjzSq2907Bfz pD2xfa4fvqI1yxfQzMx46QF6zYy2zam2 XBEbSKNxSamW8VN60oItbozLbQ4pVwyX Ni1k/WKZHrJ+ppKSDtQhqN4CvxvyybL7 5ovtJpAKYEGsmvoZqoz33FcU2HxleIun 8sKzXaNJ8+tXlrKoicZI6A==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20170715000000 20170701000000 50124 . JziSSYhqZP9eOAenLKp28LqcrAe1gs3K eWhGpOxX/u+hCnQFhuJZm2bZHomTh8Il IwQ9eKZizGQjG2BVIWhMJEWCUaPuUul1 Iq7hW9sSilivZNjef69I0xbA2Mk9MyrX 3rRUl1c5y37u5olOU7aNUAdlS3A9Kvvv dOuCWoOiVjjequaZi9JKEwHagk9il2fF HGT8/XMfIyNl4w2AvzHJ9G5oNA+HaH5E npbr8opWSxsxu8W00ne1PcDtMnmGtrPH Is1hIs/JfYQFP+0lsXU5L8gs5nfZVXgj CdluHHCnynlfp9omhbF657Mh6RMqZE/Q r+eKi0Tti/Z5W3xX2SMbWg==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20170715000000 20170701000000 50124 . g9NhvW/Ka1w9I9JPTktcJsmeDJAxAJ4i A+Eot8m7I4HTRQFHLD7DJHFB2pxesbJs WgdY+y38RJ4SDi4NodmEB60FSyNptQxO gsVYRaPclfZSjv/YhYOztns8aeZP6Qrt 4ETFI/A6ABwDjL/1ATk8zsgdhgP2u/dO d0s8pz7reNfUAf7/BsLER3pF8y23yYbp afZaVgZ1N5xj4EorGdDYhsOIN71dzAjK xaqdmj/mW/AcoAj8F+cRjVNt8vJvH7tj KiMU42hLne6RNHTCod6xn2wpZzwUB2OO kv73wEP+sJuWW4gMLpUfuGBltq5QlYH5 JAVl1V93KKNgz5szeIEpAw==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170715000000 20170701000000 50124 . tNv9R7ZphTo6s/yHsQKEZkoCK47KCPL8 p5nlc5Y0gHST42zu17SuRLKz4UQOSGjV RzFpSE7NxtERLeMhIqtEX/z+yQJ9EVJr exx/7/Ihlq4cRFzwu+7wfte+iGXXy9ZK B7kNce4ft/xgAXP9X1afruFlClb+gqar M6PETTuluNkdjTQRsOD8F62H6CXnLQQI QEMTQ4ZfAdxzrXcuIH+QHW3daMm8HfXz x9+15bVGWofeQsnZASTrK+UmcMPwu8Em z73me3YF+eKW4mMoYcQphI73wa+MltaW 5LO27IThIo0VVrtuQ2dgYg/d4J6zPUo1 k9vUS6eWFHSE5yNTu1YDyg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20170715000000 20170701000000 50124 . mlDK2ln5t2Ql7/neef2Pzz1URLXCV5ml 0QYXbt2kzrgeKqoHRXMGc6nxqYv5Ttpn TtY82BhNlYuTzubWrrg+SS7z+vSAwUM2 mXG16Y0Z3PoKc/25dmYElwcaG1h10elI unPy1GKGoEkUlEtT/6nHQdkhvyUL5QcX AwjljLIxsY7BnzhmQEsz0ywMrTmg85vX H0BOK0IFBKxLBncx10wvJGApaVzaxdWc 0H/+fnF1r/rg2NvRFuIXlz4vrl9ExNMa ZmXR4PLlZTHO3FaH9yF18IHPCY/bi8L9 NjfW9pj+Fg4LR2kPj5WCkrCR9RKhdUAO xJKLSBuSi0CL1OyLWg9QMA==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20170715000000 20170701000000 50124 . gHVrjgFrTN3p4a/4C7yyqNQKYxsIGRKH l85pqif1AoXjWQfRpVpX7JvjQ0TMJwFK GRyKpcuy6wofSg9HglcaJ6xd2BSv0Qx5 OTz1DD0x8/Vf7BDyR8S0X7CUKkkmxKt4 VjMzh1vZxktVh32d562hOCaIXaL8obuB 0+oYABZTT+6qc6a7dBAhtzg6e0qtH9vg o1v/0JXudTY3L1Gv92hBI+51/nfPZwaB Tea3VbwYdG9YzqAYenVakaAvtVvadcMD 8vV8Gjx58CaPxvIDoA0z7EYGvY3VSaV8 hL+KIV9x8bV8MVa++ykwDcgcXInp3Sg1 zUhrGGoTGRnY/dC+cUpjoQ==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20170715000000 20170701000000 50124 . g9NhvW/Ka1w9I9JPTktcJsmeDJAxAJ4i A+Eot8m7I4HTRQFHLD7DJHFB2pxesbJs WgdY+y38RJ4SDi4NodmEB60FSyNptQxO gsVYRaPclfZSjv/YhYOztns8aeZP6Qrt 4ETFI/A6ABwDjL/1ATk8zsgdhgP2u/dO d0s8pz7reNfUAf7/BsLER3pF8y23yYbp afZaVgZ1N5xj4EorGdDYhsOIN71dzAjK xaqdmj/mW/AcoAj8F+cRjVNt8vJvH7tj KiMU42hLne6RNHTCod6xn2wpZzwUB2OO kv73wEP+sJuWW4gMLpUfuGBltq5QlYH5 JAVl1V93KKNgz5szeIEpAw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170715000000 20170701000000 50124 . tNv9R7ZphTo6s/yHsQKEZkoCK47KCPL8 p5nlc5Y0gHST42zu17SuRLKz4UQOSGjV RzFpSE7NxtERLeMhIqtEX/z+yQJ9EVJr exx/7/Ihlq4cRFzwu+7wfte+iGXXy9ZK B7kNce4ft/xgAXP9X1afruFlClb+gqar M6PETTuluNkdjTQRsOD8F62H6CXnLQQI QEMTQ4ZfAdxzrXcuIH+QHW3daMm8HfXz x9+15bVGWofeQsnZASTrK+UmcMPwu8Em z73me3YF+eKW4mMoYcQphI73wa+MltaW 5LO27IThIo0VVrtuQ2dgYg/d4J6zPUo1 k9vUS6eWFHSE5yNTu1YDyg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20170715000000 20170701000000 50124 . MMOSQjU/uKwroJnNJgwb9ddyp/P5VUBr S9F4RMcPYvjqVEczfkwTMZCZvHU6S+kV AoYuMJsWhqja/YLAL9l6h1c3DJM2apVE 59ro3u1k6NeKBghVNcRscbpYeI0jaZMD 6c5r2dBtB3sE0zUPV2feCfsyV1wBPxtr 8XWBOVYu30LYXCath13F+DqXEkTNudzQ oCHixJQ9y71QXJniXgDaRxq9l5iqA4Q4 nt4zhIWntHClUJRwpQlJVU34eLiDrO6n 1s2oVPXKLwa2/mG71afpAgsOiCvJDLeH Oa2AqjAvQ9yUDIjNoaOvLv0mrp4csBSj ZC0HIMMDWqHES4UoinY4tw==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20170711000000 20170720999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20170725000000 20170711000000 50124 . Ioju8kIHgI70wBcbEyZZLbb61G42MnKd wGLxNNhBctq03x74tpqlR6HeCMJf4PW/ UHO4QqubeGK7m6TAfWvx76NKa4bYNYAj S4w+Izv5V57GL3u6BtawIdBLJfHGMLyA Aifn9xc8BnWv1Fw0k09mzXb99EC2Mc77 n99imV/I8B5sX1qi3z7bBj0pFGwvyILV FDBqFIU+kq3S78XEXpTFYInoPnDGhqAj 4g6H1QXAdW89GqkCATBKA642xI7jZ8pA 1AMlHo/YhkYr1rPQx27kGLKQJgJVpPa7 +Dh4+hy+DBRYZUUldqV4Ee+TyTT/nh36 VylsuIjbQYN801N5eFw4Qw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAcHrmeeZUfVAS5KzAL2mHyjpdS3y 5Du2ScdXa1nunnaA35ARZR6BbwuQdIik CC1LH8D7d/AlDW7SlkHWeWBK7eMxO0Ps E2u9jlN2yy+9OQwo1kybg4cBDwbgHmu8 ppX7U3S5SpP9XZIJhYRYqNube/zkoDSa uLaeghU13CCuIwdbJrPNOC6Zvn0op3WL ryTVMnMUzgld6WsiTWZDlaMy5IJdhg5R X2wa0UCJjr1M3kguTHftYExgUKbQytx2 +i1mq+vSKqcS9Bxo1vFTWMtFV2FFpV8T Fv4dL1aC1Aq8e9CiAIEfCVVy8Dw+vYpO VDrd+1qkNorMNetwEly9EgkWgB0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAa7l/x0mKre1XGN10bhnsSuo+Lc7 wFv6ksfiDz/kLgbR1TQY37ntCm3akRr/ hp3CE1YUnAGlk9B5lZYEeT0hoD2BkD7E P9DpOxUqKtQeYhe34pO/ygT+dFYSaMy/ 0OStoYLphzbIfElrpg+gvv+CkFKQzO5W rAL1tWu037erXbhSjko7AokpjfEWyT8H o7qGHiW1vWgzWGtWIgXOdydUJYqQNaOY cddJNV23fod6+KrKE245JRv+KabqnLJv 3+D6g7NDh+D5uLiX4vcTsLoB3LiAruQA r+ickjYFsVvfJlJ3m9O0/st9UsXu+v0e 6Esl5DmWWYo63T31sgmSEOjUSBE=
+. 1814400 IN DNSKEY 385 3 8 AwEAAZxJIBnv0pOId/Ukgr0pwVLXjiBR RyV0kc76BssLTIFPWnEWNLv7+3JRvcAm tpHzDvpCoX92taEYVP3pgF3WUNA/LOJR iYIE8taQ0j0TpbdiizFtgFCc+zE5TZ6z /Ru1Eq6VXgdpHzpPtTep0+gB1Qz6HQNT Wd0v2/XQmKWkh3KnJuDSrBjQy50ax9zX 1Sj8Syv+oA0iREf886Bb3e1nBaWrw9RE xovh2F68eE5hxwBcipwNyf7iEN8Um8Tj KCXWeEnTM+Giip0F+JgbapEKr8Dk5YYv nRBLwPZiekL35AbGA9/8PfCwtAIBAj3A CVHhgmlPbLzPGRyawTtIe9D/6uc=
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170801000000 20170711000000 5319 . CznzeEi6yFncw3D4NCZhUJTGf6yB9Gli tODI6UUbpntDKgdRXAWPaP13gfDUcUfb 1rhNeVJdAgxgcosDaXc3QIU1hOe85jra Z76D2hVP8/jRnEaJVaClvD7qtWaIP7bV z0JvH/4fDysSAVUySZGju+YwZfKtw8Xa exFhMJ/cqHmKV8KKTwvFddmWYSdiPGCY YokbxblHQClFMoO5FtNbVbNlRXyi8LcW 6YXNpyAyuDlYHxTc/6ilCC+fE3VzAIb+ 7b2r3NZzVkorSLurJBSHGI5vvqeOtoZX LiVpim9RASXbbP6XHf4ycsmWg9t5Eyud 7boxwvlRUlhe+he/li2ucw==
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170801000000 20170711000000 24784 . UUk7x64kvHURXPoiFHokjEta03vK89s6 xPC3dclk0TkVg+wSIMdI7eGuaZd+YYzJ 5iUYQBbLak4zZAbfq/wFl+sIoxt4Zmh/ Ja/T0LEhRqFru3/F0WcXmN39gPlmRqoH hogy7cxuAlj+L/MZf0JQAdUeb+RO0e55 ELpZ5kErYhQNG5/c2rdIg/PVV5DK638g APJ6Z0xDxqkmlBeuiMM1jrCytnmBPNHl HdR80VKFIeNL0ss5nsZzeDl4xLyqhleM K9wfmXwfySLu/ZZdfNQl5/qZMCKuDcTz 8HJ5dFmRl3nu3M8urLzYHj1lxdWfO+c4 lcjkT4S6bsozH84z6HymaA==
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170801000000 20170711000000 50124 . scFytgOyp7j+yR1QC5dSQEqzbUEeq99b aNoz8KoS1u49CM4IkxnmMiWQ4Ocs7Lc1 X0P9i6WgYEVIlo7OoRwFMGoxtsrG4rSs Crg3cCopCLtinGugO5pdj4zQi/SWIjy7 Ydq00Qrq9W25QAr4Z6BlvF85RYb7LSUi 9e3wbL2cFprG4OGP4cyMM2sByPPt/H0S dOsNEppsInTA61EYD2siRfbt9vEXOY+C ikcJ7LACBJ+ga5D+7Wsr8/tmEHb5Zd/5 8Zl6RlL1F8Zj/MvZT6a9GEM+vSkUWWln HsPGwhvoWcl/z9vdQag4E2Vaw6MUY7zj p4Mls23OJv03FbCvpWogtg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20170725000000 20170711000000 50124 . sL/q7IdMSJir2sAkL53PcYcmec33LeGk j3V+GmlvwrK71usBnz4Hp/xZ+hlyqjnX +y9qmNRMLngfW44Kmp/U3A3db3CSHJ4t XRYDjMbuR0UI2Y6zX5I3/v5VYoDEnV4R Amij1UtEQDdqQRe5BHoikUZHyN+In14I sRNTqjWgKd7W4jxJtmRVz1VTc86O9UmX PmreNjP/w6OJsfE/uozxxdOXk5Pnf5Ln 2rr4NO1kkhrFVf0VV0UNOBijkJIHEoRu 94/NMaAC3asuOW/rXbScnW+s6iX3kTIb 3LjiAks44uudRgTXcpgwnSrPhPkW/7Ct O+sXGZr4bY3C0r+rjkkirg==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20170725000000 20170711000000 50124 . roCz+GbpvVzD1RHy0RU0ji4iHBL36x0D bcWhC2PGj14AzX2j6OPlpDG1dRf9X4qz Es73u3IZjYRb3ZdQE0V/lBXpqbCb+Usp SGdj0huP7j0aNRawNmSKTimx3eSzhI1t CCmsYiavH1GpFUKQdLDP0vBq3OHxJHBM GP1ff6scTG9tBKzZtr7ZCdFK8hH9Z6Yr bOkXmRMM11rKMRjB9A0qBcVknWclDcY+ KajxfhUsjvAuGsEqQf58ekU6PPYXNmy9 lK8LRTf4UDNt86qjY8MRflhWN83cRc8g GCNKSGqXZ4yqZnQF2gbACVH7LY3YOLKe 7Rfl+CYGJACMLfzUJ7B6Kg==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170725000000 20170711000000 50124 . oR5QQvmd3XU4E/njBMZq1Px+iHgKla9/ /PRteMz2c2gqvQBX7gMk6+6ybgQ10IhA cAE4xflPtOdLS7Wvb3OP2LA5Sdi/vxwZ R+2bHHwPAtkbP8AO/70mcWIW5MIBqPD3 bQkRmTDnBylNRU3j+DPu6xew+f6DzUPt cSRrm+8j54y44yNB4lV9yppoXYEibX/q xIIqkauYEVETVtmzTmB4PjWDTriA0b9z 0eZetan+z2x0GqnBaQT/3a+cn6xyZA4X JwDJ0n7dL/VVJbJwb2/ZziMRO2ng4M5F RMWlD6aKZkBKn3CLDtzVOhG+HO243ivO vzTg72aTIVkUQOj2k0sjbw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20170725000000 20170711000000 50124 . Ioju8kIHgI70wBcbEyZZLbb61G42MnKd wGLxNNhBctq03x74tpqlR6HeCMJf4PW/ UHO4QqubeGK7m6TAfWvx76NKa4bYNYAj S4w+Izv5V57GL3u6BtawIdBLJfHGMLyA Aifn9xc8BnWv1Fw0k09mzXb99EC2Mc77 n99imV/I8B5sX1qi3z7bBj0pFGwvyILV FDBqFIU+kq3S78XEXpTFYInoPnDGhqAj 4g6H1QXAdW89GqkCATBKA642xI7jZ8pA 1AMlHo/YhkYr1rPQx27kGLKQJgJVpPa7 +Dh4+hy+DBRYZUUldqV4Ee+TyTT/nh36 VylsuIjbQYN801N5eFw4Qw==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20170725000000 20170711000000 50124 . hvFKqAWhl5GNl1r8yPMgCGDiskPKPBCx xzWWfoeZZIzhAXgeZGdCsd/5tS3fifaX Y7Wn6rMi0BstLa/+63xaY4iIvX36Zb+n eLxaKrtjr06Pnappg6EHDvF2qjEHf+ip pIif7yiX6UZemLK30E/mUhkL1HFtc9eW 9ZGdTI4VUyJvhax2qW4fcoYNgPeLeB+i LrdjyfKXB3tnrcZbmVqKa7OQX+JNL9yN s5YHbftvraZt9p0Ye+A8wwsvKE1eOlyd msdylrtlDz3JdKBsAdQm9ek5Ltpuvbht xYyvhh72c8RpO9aUUP1ehT9rtH6NeA3U dBooOLGimGRzJJ3BNYuBig==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20170725000000 20170711000000 50124 . roCz+GbpvVzD1RHy0RU0ji4iHBL36x0D bcWhC2PGj14AzX2j6OPlpDG1dRf9X4qz Es73u3IZjYRb3ZdQE0V/lBXpqbCb+Usp SGdj0huP7j0aNRawNmSKTimx3eSzhI1t CCmsYiavH1GpFUKQdLDP0vBq3OHxJHBM GP1ff6scTG9tBKzZtr7ZCdFK8hH9Z6Yr bOkXmRMM11rKMRjB9A0qBcVknWclDcY+ KajxfhUsjvAuGsEqQf58ekU6PPYXNmy9 lK8LRTf4UDNt86qjY8MRflhWN83cRc8g GCNKSGqXZ4yqZnQF2gbACVH7LY3YOLKe 7Rfl+CYGJACMLfzUJ7B6Kg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170725000000 20170711000000 50124 . oR5QQvmd3XU4E/njBMZq1Px+iHgKla9/ /PRteMz2c2gqvQBX7gMk6+6ybgQ10IhA cAE4xflPtOdLS7Wvb3OP2LA5Sdi/vxwZ R+2bHHwPAtkbP8AO/70mcWIW5MIBqPD3 bQkRmTDnBylNRU3j+DPu6xew+f6DzUPt cSRrm+8j54y44yNB4lV9yppoXYEibX/q xIIqkauYEVETVtmzTmB4PjWDTriA0b9z 0eZetan+z2x0GqnBaQT/3a+cn6xyZA4X JwDJ0n7dL/VVJbJwb2/ZziMRO2ng4M5F RMWlD6aKZkBKn3CLDtzVOhG+HO243ivO vzTg72aTIVkUQOj2k0sjbw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "it works"
+test. 1 IN RRSIG TXT 8 1 1 20170725000000 20170711000000 50124 . UFS44wefPpNwYGd5AtJL2LgjnL2HJs19 po0R+h6WBuxSoSLnMjg0gYHaiN26LNQT Wgs9A3DsW9I5mJJZeh4ZPNzzxWH0MSGL nvyRVwSPj/WGhzhwHTDReJ5cAKzOUD3H qxr9nYEyd3PWbRHY4SgfAfbR+qv3uInN MGcnX4+/8HPYcmcyuS2E7XdEv8teFBXr +evmJzQiJAqrpQ4maUlz6hKeJjOWBvKW Ta8neWaO6rqnmSQjO6h0SGSdhfeXUnqA 6LwmkV3Gqy1Dt7kAAzLVQhYvN5w+nqj7 26NHTZUQ1yXb1fCrdLpVa6a14bgDJQsf leRh4tR0P5H9MPjPi9M6ew==
+ENTRY_END
+RANGE_END
+
+
+
+RANGE_BEGIN 20170721000000 99999999999999
+ ADDRESS 198.41.0.4
+ ADDRESS 2001:503:ba3e::2:30
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20170804000000 20170721000000 50124 . dRkNimO+IbMX99xfiauuzh2Z+fpuvVis EUvJMqJLTvomlofQhQVJbjNvnjNP2FcL MKEumKdDY9sLR7xAPc9h3lmp3NC7L0Q2 kY0uoxitDD5NCZJY82HAn1A4LPvZaFYs 3fIgA1wpz/GipIVDsZ7LXN8fqdpvmxNK l8HgU+t4Gp8X/uDGjFW6TrY+AlN1LYmd rN8dMXuA2iajQob9WyBDQkmDrbRZ7iJ+ a2JCQqJfIaBFEY7gc9JphxS7ehlaLlqK MMlLH3Nugh+gNLToDdeUXYUqBvefP0/U CGw8dhAIgqC3x0wn2fgYxSsJBdmLsRGt Lv+mISIfw6Ay21Kmi65Ytg==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN DNSKEY
+SECTION ANSWER
+. 1814400 IN DNSKEY 256 3 8 AwEAAcHrmeeZUfVAS5KzAL2mHyjpdS3y 5Du2ScdXa1nunnaA35ARZR6BbwuQdIik CC1LH8D7d/AlDW7SlkHWeWBK7eMxO0Ps E2u9jlN2yy+9OQwo1kybg4cBDwbgHmu8 ppX7U3S5SpP9XZIJhYRYqNube/zkoDSa uLaeghU13CCuIwdbJrPNOC6Zvn0op3WL ryTVMnMUzgld6WsiTWZDlaMy5IJdhg5R X2wa0UCJjr1M3kguTHftYExgUKbQytx2 +i1mq+vSKqcS9Bxo1vFTWMtFV2FFpV8T Fv4dL1aC1Aq8e9CiAIEfCVVy8Dw+vYpO VDrd+1qkNorMNetwEly9EgkWgB0=
+. 1814400 IN DNSKEY 257 3 8 AwEAAa7l/x0mKre1XGN10bhnsSuo+Lc7 wFv6ksfiDz/kLgbR1TQY37ntCm3akRr/ hp3CE1YUnAGlk9B5lZYEeT0hoD2BkD7E P9DpOxUqKtQeYhe34pO/ygT+dFYSaMy/ 0OStoYLphzbIfElrpg+gvv+CkFKQzO5W rAL1tWu037erXbhSjko7AokpjfEWyT8H o7qGHiW1vWgzWGtWIgXOdydUJYqQNaOY cddJNV23fod6+KrKE245JRv+KabqnLJv 3+D6g7NDh+D5uLiX4vcTsLoB3LiAruQA r+ickjYFsVvfJlJ3m9O0/st9UsXu+v0e 6Esl5DmWWYo63T31sgmSEOjUSBE=
+. 1814400 IN DNSKEY 385 3 8 AwEAAZxJIBnv0pOId/Ukgr0pwVLXjiBR RyV0kc76BssLTIFPWnEWNLv7+3JRvcAm tpHzDvpCoX92taEYVP3pgF3WUNA/LOJR iYIE8taQ0j0TpbdiizFtgFCc+zE5TZ6z /Ru1Eq6VXgdpHzpPtTep0+gB1Qz6HQNT Wd0v2/XQmKWkh3KnJuDSrBjQy50ax9zX 1Sj8Syv+oA0iREf886Bb3e1nBaWrw9RE xovh2F68eE5hxwBcipwNyf7iEN8Um8Tj KCXWeEnTM+Giip0F+JgbapEKr8Dk5YYv nRBLwPZiekL35AbGA9/8PfCwtAIBAj3A CVHhgmlPbLzPGRyawTtIe9D/6uc=
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170811000000 20170721000000 5319 . j9HlRZ3AkCZDpje5AQCXS4N21LfKzqFf c871WzN/OgHF2CO6xDQ3fMznWttRprz4 rpFBUDtXWWyCaLtjzNSrv7XFw6ui3Y05 jdAfAqhYkFQ1NeH6MG/tpZyWGDocJhiq 1hwcgJ9E+IZsOleyziYD3/vrFGKel3Ou qMd7n+T+zLzNjBnDymtSu+sUzWn3A6wG rgHbxO650aLvD4uIQYVEKqLJUddEUwOv 1Yy/Td5RDGbxHLc6L0uTyYkIFc9u4zzf bMpd3zJFSgcSsOkwkd9K+GKNPx2LXisH 01VwpermkzR3bVyl2NTglCy1CjB503gS ePdb1YNZAFUfaNzK0j5mUA==
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170811000000 20170721000000 24784 . JWuXNkU74/Y8FqZ9EaQr6czGQDGUhcnw PiwIi3JV77zy34KE0jwWj7AXXdOARk9L 2IfKG+ykFDej7ft85L/z4uViWBICcRrN E9RVKuLkNmzvCtQD5rp4rGdtnNP80qD/ 7Iql62/Vl1atftBzO3uUTY+1DL3O8IQD ry1hGmWRt4RLCFOl1VTUFa8TTf3p0QgK aoD+usTf7x5/Tiy228A36exmsEAG+Xfo g2QbYnN8sxe2w2Jce5ete30eA1QGxxGr cF1vN5v8wbrFYBCSmKLKqtJtUhCZyVX8 vcZofvXa7ufaSdfGURoi/hYT3OCFoeiM tOjEYUttSgdjJycDB3P3Ag==
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20170811000000 20170721000000 50124 . qOnGDMrqd2LZ1+TkpaGopSBXmabYt82D JvhEtUZIVayOGAvAVt0rK5VsE2YffIFy pewgkBVs7aGds4X7SX1x7EEI/ovrgQlR A2fIS7BvknDFsE4mZ2QEmR8C1N2WA1Gu ddot0cjx0kgLL8VS9Kycy+QoTrROFa5Z 3JHiGfzS1lTQuCby29Ne2GKT0edjr9RD 1QuXugwEmZvgbW0+6EcZCeYoEo/o+0MG It9Bf6mJqKG8ni3xLtQORglhn1rWutkh kzhpC6rm0mXcv7VgwXiFj+smHE5+rnDf CSR7ke830+2cSr6Kd5dSOI0ohRLYH+5p H5Oc4A0eFoD0grg5CZ/8vw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 518400 IN NS rootns.
+. 518400 IN RRSIG NS 8 0 518400 20170804000000 20170721000000 50124 . qYBWDglfJ252SaOEo6+wHhXd9xo743sq rbF2csOGAvEmd+TAHQ0T+I6acH2AdOmf 088pBeQFkKodIBeBcdq7HPXK9v4A5ld+ OVWYmXxiNdLreOHsn/lw4hybbYu5iiwy hathQKOolfg7sS9evVB+YUGD6GLMTyDt vIkbbB5750khnO27rF1Ud6wWeayDOwUv rhriU7cjCPN4G7yo2FiZYDA0ZhOafTOo TgESBEREPRGZaOVjLIV16g8EGr1rche3 AfMfam0bAfYdkdU2scSCXDfB78MprnCN otAd2ze4VtbjZdxWq4Ji4XfleIQ7ICXQ 2Ao7vchX9IpME5vdNLvAig==
+SECTION ADDITIONAL
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG A 8 1 518400 20170804000000 20170721000000 50124 . m2MZgby1HEHD9nRhRvprWdBMQtXBjPQU vpom5LA2iF5CIect6fF1pHG38ckRz7s2 hsWb8QLic3CtAqB8nxkHPiS8rfTdlN7N +3DEXEaurAB4RCZFmpukK9aqwasW1BLI Ul7eYgvihgo6aErN01IP5Thqqrd8b/SS 6RcCtk2Cfdyf7jMwSAR8D1RJxuRqoyOe Gofk2yYMm6wuBStVRtLvJwaAk3rsyWRT jmx4tlKijIYZvjZ2Iusnr6+rptQmNMqd xtNSrREbB94m6WZbsviy4rdvx7rq18LA mq+eB+7ROz2/tSpUkrXL7nFF09+Fkztx Tz2JRKV/Ee7LKpvw7v91Hw==
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170804000000 20170721000000 50124 . mz2zvQhPZsyQ2YS5GuRVTYwXl5daG9uY 259B2cJbfNRIsiWtpugPnnITtKMABZUY aHP7IaR9j+LsIhNL2z3u/e0H+bmMS0Mb +w3qEhrx6L6h1h4mZAeHN31iUP1kilbH TlBHTJkPeh6xM9+BX/LH7LPozqFl3Nai 4iqv7oG+niQ5rb758bAG8AM5jQ5WyPev ZmkAqhkQiQrkpYRsVtUkNNoTTGrTytUM LVwFUC8KRAmJzavUlHXeucUXWLkzZFYw 07lNug5/8ZdCPlpv1+bdBKa1Bth8f5Bn 269xi1PZX7OV22luv2dnoU1/+xqQ0su2 NRHeLG39gD7JnOE2BC5Tlw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN NS
+SECTION AUTHORITY
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+rootns. 86400 IN NSEC test. A AAAA RRSIG NSEC
+. 86400 IN RRSIG SOA 8 0 86400 20170804000000 20170721000000 50124 . dRkNimO+IbMX99xfiauuzh2Z+fpuvVis EUvJMqJLTvomlofQhQVJbjNvnjNP2FcL MKEumKdDY9sLR7xAPc9h3lmp3NC7L0Q2 kY0uoxitDD5NCZJY82HAn1A4LPvZaFYs 3fIgA1wpz/GipIVDsZ7LXN8fqdpvmxNK l8HgU+t4Gp8X/uDGjFW6TrY+AlN1LYmd rN8dMXuA2iajQob9WyBDQkmDrbRZ7iJ+ a2JCQqJfIaBFEY7gc9JphxS7ehlaLlqK MMlLH3Nugh+gNLToDdeUXYUqBvefP0/U CGw8dhAIgqC3x0wn2fgYxSsJBdmLsRGt Lv+mISIfw6Ay21Kmi65Ytg==
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20170804000000 20170721000000 50124 . En0/8voZSHLFgopg9yKSKo+IVbyZVVEC kHPBNEDqen7oRAt7pUqYDYFKNURQ9gii BD68xxSgQltFxzY5u5ml2QA1J/I59AuM gUgm6FznTzD0Td5sA5mMNKs6l1Kn4IvR PIJbsgth/RqkTFtyJ44aHqxFCYAVbv46 PiX0aEMRNx+exvHhCjLwpNDuHJ0msX7m rHWRwOr2kVOv+KXLFsInfutUQah8Ujpc 4urpFJ0qF4QbkioVhl+5jD3UanvY1UqL gD2g0p23GJSIJXyuPAEsJOeq4PrvTcqk b48TWOlsmpKwy+Nd9p/eUdpqr+b0XFw0 mm7JBJnz7fsqMapcvipwVw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN A
+SECTION ANSWER
+rootns. 518400 IN A 198.41.0.4
+rootns. 518400 IN RRSIG A 8 1 518400 20170804000000 20170721000000 50124 . m2MZgby1HEHD9nRhRvprWdBMQtXBjPQU vpom5LA2iF5CIect6fF1pHG38ckRz7s2 hsWb8QLic3CtAqB8nxkHPiS8rfTdlN7N +3DEXEaurAB4RCZFmpukK9aqwasW1BLI Ul7eYgvihgo6aErN01IP5Thqqrd8b/SS 6RcCtk2Cfdyf7jMwSAR8D1RJxuRqoyOe Gofk2yYMm6wuBStVRtLvJwaAk3rsyWRT jmx4tlKijIYZvjZ2Iusnr6+rptQmNMqd xtNSrREbB94m6WZbsviy4rdvx7rq18LA mq+eB+7ROz2/tSpUkrXL7nFF09+Fkztx Tz2JRKV/Ee7LKpvw7v91Hw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+rootns. IN AAAA
+SECTION ANSWER
+rootns. 518400 IN AAAA 2001:503:ba3e::2:30
+rootns. 518400 IN RRSIG AAAA 8 1 518400 20170804000000 20170721000000 50124 . mz2zvQhPZsyQ2YS5GuRVTYwXl5daG9uY 259B2cJbfNRIsiWtpugPnnITtKMABZUY aHP7IaR9j+LsIhNL2z3u/e0H+bmMS0Mb +w3qEhrx6L6h1h4mZAeHN31iUP1kilbH TlBHTJkPeh6xM9+BX/LH7LPozqFl3Nai 4iqv7oG+niQ5rb758bAG8AM5jQ5WyPev ZmkAqhkQiQrkpYRsVtUkNNoTTGrTytUM LVwFUC8KRAmJzavUlHXeucUXWLkzZFYw 07lNug5/8ZdCPlpv1+bdBKa1Bth8f5Bn 269xi1PZX7OV22luv2dnoU1/+xqQ0su2 NRHeLG39gD7JnOE2BC5Tlw==
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode question
+ADJUST copy_id
+REPLY NOERROR QR AA DO
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. 1 IN TXT "check and change answer"
+test. 1 IN RRSIG TXT 8 1 1 20170804000000 20170721000000 50124 . MrLc+GLJYf5OB/rXAtNUzB5vXAvDVIv0 uVzq5QtWij6/GVPja0gsgB0+ZeDusszf RNwFrcJi8wxxBwSAoQWr1wnwvSCODZUG i+8bsVAlP/BysUnajLZdhSUO6LsLhAl/ qCh6wTSbCHa9jp7wODEnRCanXNAl4G2J Q6arffoRd0oWG4vGFvGKzcQSijaIlByI cTxSWyclJRYCyPofPzMq+dKyB0l+kCCr sCy9ke/dXhl8wv6xw7u1UquqwoMp4xjF zNeXdwXdUdZisl9eQIKxFqWpfAKQCRgh 2ZNCHb2Q8ljAxR2Uyj7A06A5pMjTFJlQ sH7FEAlJ2LGNK/nRf8wXGg==
+ENTRY_END
+RANGE_END
+
+
+; 2017-07-01T00:00:00
+STEP 20170701000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170701000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170701000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-02T00:00:00
+STEP 20170702000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170702000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170702000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-03T00:00:00
+STEP 20170703000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170703000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170703000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-04T00:00:00
+STEP 20170704000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170704000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170704000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-05T00:00:00
+STEP 20170705000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170705000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170705000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-06T00:00:00
+STEP 20170706000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170706000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170706000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-07T00:00:00
+STEP 20170707000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170707000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170707000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-08T00:00:00
+STEP 20170708000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170708000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170708000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-09T00:00:00
+STEP 20170709000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170709000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170709000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-10T00:00:00
+STEP 20170710000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170710000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170710000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-11T00:00:00
+STEP 20170711000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170711000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170711000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-12T00:00:00
+STEP 20170712000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170712000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170712000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-13T00:00:00
+STEP 20170713000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170713000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170713000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-14T00:00:00
+STEP 20170714000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170714000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170714000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-15T00:00:00
+STEP 20170715000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170715000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170715000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-16T00:00:00
+STEP 20170716000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170716000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170716000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-17T00:00:00
+STEP 20170717000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170717000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170717000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-18T00:00:00
+STEP 20170718000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170718000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170718000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-19T00:00:00
+STEP 20170719000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170719000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170719000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-20T00:00:00
+STEP 20170720000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170720000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170720000099 TIME_PASSES ELAPSE 86400
+
+
+; 2017-07-21T00:00:00
+STEP 20170721000000 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+STEP 20170721000001 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
+
+; move time by 1 day, 0:00:00
+STEP 20170721000099 TIME_PASSES ELAPSE 86400
+
+
+
+SCENARIO_END
+
diff --git a/modules/view/.packaging/test.config b/modules/view/.packaging/test.config
new file mode 100644
index 0000000..b639fda
--- /dev/null
+++ b/modules/view/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('view')
+assert(view)
+quit()
diff --git a/modules/view/README.rst b/modules/view/README.rst
new file mode 100644
index 0000000..daffd30
--- /dev/null
+++ b/modules/view/README.rst
@@ -0,0 +1,92 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-view:
+
+Views and ACLs
+==============
+
+The :ref:`policy <mod-policy>` module implements policies for global query matching, e.g. solves "how to react to certain query".
+This module combines it with query source matching, e.g. "who asked the query". This allows you to create personalized blacklists, filters and ACLs.
+
+There are two identification mechanisms:
+
+* ``addr``
+ - identifies the client based on his subnet
+* ``tsig``
+ - identifies the client based on a TSIG key name (only for testing purposes, TSIG signature is not verified!)
+
+View module allows you to combine query source information with :ref:`policy <mod-policy>` rules.
+
+.. code-block:: lua
+
+ view:addr('10.0.0.1', policy.suffix(policy.TC, policy.todnames({'example.com'})))
+
+This example will force given client to TCP for names in ``example.com`` subtree.
+You can combine view selectors with RPZ_ to create personalized filters for example.
+
+.. warning::
+
+ Beware that cache is shared by *all* requests. For example, it is safe
+ to refuse answer based on who asks the resolver, but trying to serve
+ different data to different clients will result in unexpected behavior.
+ Setups like **split-horizon** which depend on isolated DNS caches
+ are explicitly not supported.
+
+
+Example configuration
+---------------------
+
+.. code-block:: lua
+
+ -- Load modules
+ modules = { 'view' }
+ -- Whitelist queries identified by TSIG key
+ view:tsig('\5mykey', policy.all(policy.PASS))
+ -- Block local IPv4 clients (ACL like)
+ view:addr('127.0.0.1', policy.all(policy.DENY))
+ -- Block local IPv6 clients (ACL like)
+ view:addr('::1', policy.all(policy.DENY))
+ -- Drop queries with suffix match for remote client
+ view:addr('10.0.0.0/8', policy.suffix(policy.DROP, policy.todnames({'xxx'})))
+ -- RPZ for subset of clients
+ view:addr('192.168.1.0/24', policy.rpz(policy.PASS, 'whitelist.rpz'))
+ -- Do not try this - it will pollute cache and surprise you!
+ -- view:addr('10.0.0.0/8', policy.all(policy.FORWARD('2001:DB8::1')))
+ -- Drop all IPv4 that hasn't matched
+ view:addr('0.0.0.0/0', policy.all(policy.DROP))
+
+Rule order
+----------
+
+The current implementation is best understood as three separate rule chains:
+vanilla ``policy.add``, ``view:tsig`` and ``view:addr``.
+For each request the rules in these chains get tried one by one until a :ref:`non-chain policy action <mod-policy-actions>` gets executed.
+
+By default :ref:`policy module <mod-policy>` acts before ``view`` module due to ``policy`` being loaded by default. If you want to intermingle universal rules with ``view:addr``, you may simply wrap the universal policy rules in view closure like this:
+
+.. code-block:: lua
+
+ view:addr('0.0.0.0/0', policy.<rule>) -- and
+ view:addr('::0/0', policy.<rule>)
+
+
+Properties
+----------
+
+.. function:: view:addr(subnet, rule)
+
+ :param subnet: client subnet, e.g. ``10.0.0.1``
+ :param rule: added rule, e.g. ``policy.pattern(policy.DENY, '[0-9]+\2cz')``
+
+ Apply rule to clients in given subnet.
+
+.. function:: view:tsig(key, rule)
+
+ :param key: client TSIG key domain name, e.g. ``\5mykey``
+ :param rule: added rule, e.g. ``policy.pattern(policy.DENY, '[0-9]+\2cz')``
+
+ Apply rule to clients with given TSIG key.
+
+ .. warning:: This just selects rule based on the key name, it doesn't verify the key or signature yet.
+
+.. _RPZ: https://dnsrpz.info/
diff --git a/modules/view/addr.test.integr/deckard.yaml b/modules/view/addr.test.integr/deckard.yaml
new file mode 100644
index 0000000..8170ffd
--- /dev/null
+++ b/modules/view/addr.test.integr/deckard.yaml
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+programs:
+- name: kresd
+ binary: kresd
+ additional:
+ - --noninteractive
+ templates:
+ - modules/view/addr.test.integr/kresd_config.j2
+ - tests/integration/hints_zone.j2
+ configs:
+ - config
+ - hints
diff --git a/modules/view/addr.test.integr/kresd_config.j2 b/modules/view/addr.test.integr/kresd_config.j2
new file mode 100644
index 0000000..3dd8d92
--- /dev/null
+++ b/modules/view/addr.test.integr/kresd_config.j2
@@ -0,0 +1,62 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+{% raw %}
+modules.load('view < policy')
+
+view:addr('127.127.0.0/16', policy.suffix(policy.DENY_MSG("addr 127.127.0.0/16 matched com"),{"\3com\0"}))
+view:addr('127.127.0.0/16', policy.suffix(policy.DENY_MSG("addr 127.127.0.0/16 matched net"),{"\3net\0"}))
+policy.add(policy.all(policy.FORWARD('1.2.3.4')))
+
+-- make sure DNSSEC is turned off for tests
+trust_anchors.remove('.')
+
+-- 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
+
+_hint_root_file('hints')
+cache.size = 2*MB
+log_level('debug')
+{% endraw %}
+
+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(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 --git a/modules/view/addr.test.integr/module_view_addr.rpl b/modules/view/addr.test.integr/module_view_addr.rpl
new file mode 100644
index 0000000..f9370da
--- /dev/null
+++ b/modules/view/addr.test.integr/module_view_addr.rpl
@@ -0,0 +1,79 @@
+; SPDX-License-Identifier: GPL-3.0-or-later
+; config options
+ stub-addr: 1.2.3.4
+ query-minimization: off
+CONFIG_END
+
+SCENARIO_BEGIN view:addr test
+
+RANGE_BEGIN 0 110
+ ADDRESS 1.2.3.4
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+example.cz. IN A
+SECTION ANSWER
+example.cz. IN A 5.6.7.8
+ENTRY_END
+
+RANGE_END
+
+; policy module loaded before view module must take precedence before view
+STEP 10 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+example.cz. IN A
+ENTRY_END
+
+STEP 20 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH flags rcode question answer
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+example.cz. IN A
+SECTION ANSWER
+example.cz. IN A 5.6.7.8
+ENTRY_END
+
+; blocked by view:addr + inner policy.suffix com
+; NXDOMAIN expected
+STEP 30 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+example.com. IN A
+ENTRY_END
+
+STEP 31 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH opcode question rcode additional
+REPLY QR RD RA AA NXDOMAIN
+SECTION QUESTION
+example.com. IN A
+SECTION ADDITIONAL
+explanation.invalid. 10800 IN TXT "addr 127.127.0.0/16 matched com"
+ENTRY_END
+
+; blocked by view:addr + inner policy.suffix net
+; second view rule gets executed if policy in preceding view rule did not match
+STEP 32 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+example.net. IN A
+ENTRY_END
+
+STEP 33 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH opcode question rcode additional
+REPLY QR RD RA AA NXDOMAIN
+SECTION QUESTION
+example.net. IN A
+SECTION ADDITIONAL
+explanation.invalid. 10800 IN TXT "addr 127.127.0.0/16 matched net"
+ENTRY_END
+
+SCENARIO_END
diff --git a/modules/view/meson.build b/modules/view/meson.build
new file mode 100644
index 0000000..233448b
--- /dev/null
+++ b/modules/view/meson.build
@@ -0,0 +1,11 @@
+# LUA module: view
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+lua_mod_src += [
+ files('view.lua'),
+]
+
+integr_tests += [
+ ['view.tsig', meson.current_source_dir() / 'tsig.test.integr'],
+ ['view.addr', meson.current_source_dir() / 'addr.test.integr'],
+]
diff --git a/modules/view/tsig.test.integr/deckard.yaml b/modules/view/tsig.test.integr/deckard.yaml
new file mode 100644
index 0000000..06792be
--- /dev/null
+++ b/modules/view/tsig.test.integr/deckard.yaml
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+programs:
+- name: kresd
+ binary: kresd
+ additional:
+ - --noninteractive
+ templates:
+ - modules/view/tsig.test.integr/kresd_config.j2
+ - tests/integration/hints_zone.j2
+ configs:
+ - config
+ - hints
diff --git a/modules/view/tsig.test.integr/kresd_config.j2 b/modules/view/tsig.test.integr/kresd_config.j2
new file mode 100644
index 0000000..f04dce2
--- /dev/null
+++ b/modules/view/tsig.test.integr/kresd_config.j2
@@ -0,0 +1,64 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+{% raw %}
+modules.load('view')
+print(table_print(modules.list()))
+
+view:tsig('\8testkey1\0', policy.suffix(policy.DENY_MSG("TSIG key testkey1 matched com"),{"\3com\0"}))
+view:tsig('\8testkey1\0', policy.suffix(policy.DENY_MSG("TSIG key testkey1 matched net"),{"\3net\0"}))
+view:tsig('\7testkey\0', policy.suffix(policy.DENY_MSG("TSIG key testkey matched example"),{"\7example\0"}))
+policy.add(policy.all(policy.FORWARD('1.2.3.4')))
+
+-- 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
+
+-- make sure DNSSEC is turned off for tests
+trust_anchors.remove('.')
+
+_hint_root_file('hints')
+cache.size = 2*MB
+log_level('debug')
+{% endraw %}
+
+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(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 --git a/modules/view/tsig.test.integr/module_view_tsig.rpl b/modules/view/tsig.test.integr/module_view_tsig.rpl
new file mode 100644
index 0000000..fd6d291
--- /dev/null
+++ b/modules/view/tsig.test.integr/module_view_tsig.rpl
@@ -0,0 +1,114 @@
+; SPDX-License-Identifier: GPL-3.0-or-later
+; config options
+ stub-addr: 1.2.3.4
+CONFIG_END
+
+SCENARIO_BEGIN view:tsig test
+
+RANGE_BEGIN 0 110
+ ADDRESS 1.2.3.4
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+example.cz. IN A
+SECTION ANSWER
+example.cz. IN A 5.6.7.8
+ENTRY_END
+
+RANGE_END
+
+RANGE_BEGIN 0 110
+ ADDRESS 192.0.2.1
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+example.net. IN A
+SECTION ANSWER
+example.net. IN A 6.6.6.6
+ENTRY_END
+RANGE_END
+
+; policy fallback (no view matched, policy is behind view module)
+STEP 10 QUERY
+ENTRY_BEGIN
+REPLY RD
+TSIG testkey +Cdjlkef9ZTSeixERZ433Q==
+SECTION QUESTION
+example.cz. IN A
+ENTRY_END
+
+STEP 20 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH flags rcode question answer
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+example.cz. IN A
+SECTION ANSWER
+example.cz. IN A 5.6.7.8
+ENTRY_END
+
+; blocked by view:tsig testkey1 + inner policy.suffix com
+; NXDOMAIN expected
+STEP 30 QUERY
+ENTRY_BEGIN
+REPLY RD
+TSIG testkey1 +Cdjlkef9ZTSeixERZ433Q==
+SECTION QUESTION
+example.com. IN A
+ENTRY_END
+
+STEP 31 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH opcode question rcode additional
+REPLY QR RD RA AA NXDOMAIN
+SECTION QUESTION
+example.com. IN A
+SECTION ADDITIONAL
+explanation.invalid. 10800 IN TXT "TSIG key testkey1 matched com"
+ENTRY_END
+
+; blocked by view:tsig testkey1 + inner policy.suffix net
+; second view rule gets executed if policy in preceding view rule did not match
+STEP 32 QUERY
+ENTRY_BEGIN
+REPLY RD
+TSIG testkey1 +Cdjlkef9ZTSeixERZ433Q==
+SECTION QUESTION
+example.net. IN A
+ENTRY_END
+
+STEP 33 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH opcode question rcode additional
+REPLY QR RD RA AA NXDOMAIN
+SECTION QUESTION
+example.net. IN A
+SECTION ADDITIONAL
+explanation.invalid. 10800 IN TXT "TSIG key testkey1 matched net"
+ENTRY_END
+
+; blocked by view:tsig testkey + inner policy.suffix example (different key)
+; third view rule gets executed if policy in preceding view rule did not match
+STEP 34 QUERY
+ENTRY_BEGIN
+REPLY RD
+TSIG testkey +Cdjlkef9ZTSeixERZ433Q==
+SECTION QUESTION
+example. IN A
+ENTRY_END
+
+STEP 35 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH opcode question rcode additional
+REPLY QR RD RA AA NXDOMAIN
+SECTION QUESTION
+example. IN A
+SECTION ADDITIONAL
+explanation.invalid. 10800 IN TXT "TSIG key testkey matched example"
+ENTRY_END
+
+SCENARIO_END
diff --git a/modules/view/view.lua b/modules/view/view.lua
new file mode 100644
index 0000000..f5e1862
--- /dev/null
+++ b/modules/view/view.lua
@@ -0,0 +1,121 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local kres = require('kres')
+local ffi = require('ffi')
+local C = ffi.C
+
+-- Module declaration
+local view = {
+ key = {}, -- map from :owner() to list of policy rules
+ src = {},
+ dst = {},
+}
+
+-- @function View based on TSIG key name.
+function view.tsig(_, tsig, rule)
+ if view.key[tsig] == nil then
+ view.key[tsig] = { rule }
+ else
+ table.insert(view.key[tsig], rule)
+ end
+end
+
+-- @function View based on source IP subnet.
+function view.addr(_, subnet, rules, dst)
+ local subnet_cd = ffi.new('char[16]')
+ local family = C.kr_straddr_family(subnet)
+ local bitlen = C.kr_straddr_subnet(subnet_cd, subnet)
+ if bitlen < 0 then
+ error(string.format('failed to parse subnet %s', subnet))
+ end
+ local t = {family, subnet_cd, bitlen, rules}
+ table.insert(dst and view.dst or view.src, t)
+ return t
+end
+
+-- @function Match IP against given subnet
+local function match_subnet(family, subnet, bitlen, addr)
+ return (family == addr:family()) and (C.kr_bitcmp(subnet, addr:ip(), bitlen) == 0)
+end
+
+-- @function Execute a policy callback (may be nil);
+-- return boolean: whether to continue trying further rules.
+local function execute(state, req, match_cb)
+ if match_cb == nil then return false end
+ local action = match_cb(req, req:current())
+ if action == nil then return false end
+ local next_state = action(state, req)
+ if next_state then -- Not a chain rule,
+ req.state = next_state
+ return true
+ else
+ return false
+ end
+end
+
+-- @function Try all the rules in order, until a non-chain rule gets executed.
+local function evaluate(state, req)
+ -- Try :tsig rules first.
+ local client_key = req.qsource.packet.tsig_rr
+ local match_cbs = (client_key ~= nil) and view.key[client_key:owner()] or {}
+ for _, match_cb in ipairs(match_cbs) do
+ if execute(state, req, match_cb) then return end
+ end
+ -- Then try :addr by the source.
+ if req.qsource.addr ~= nil then
+ for i = 1, #view.src do
+ local pair = view.src[i]
+ if match_subnet(pair[1], pair[2], pair[3], req.qsource.addr) then
+ local match_cb = pair[4]
+ if execute(state, req, match_cb) then return end
+ end
+ end
+ -- Finally try :addr by the destination.
+ elseif req.qsource.dst_addr ~= nil then
+ for i = 1, #view.dst do
+ local pair = view.dst[i]
+ if match_subnet(pair[1], pair[2], pair[3], req.qsource.dst_addr) then
+ local match_cb = pair[4]
+ if execute(state, req, match_cb) then return end
+ end
+ end
+ end
+end
+
+-- @function Return policy based on source address
+function view.rule_src(action, subnet)
+ local subnet_cd = ffi.new('char[16]')
+ local family = C.kr_straddr_family(subnet)
+ local bitlen = C.kr_straddr_subnet(subnet_cd, subnet)
+ return function(req, _)
+ local addr = req.qsource.addr
+ if addr ~= nil and match_subnet(family, subnet_cd, bitlen, addr) then
+ return action
+ end
+ end
+end
+
+-- @function Return policy based on destination address
+function view.rule_dst(action, subnet)
+ local subnet_cd = ffi.new('char[16]')
+ local family = C.kr_straddr_family(subnet)
+ local bitlen = C.kr_straddr_subnet(subnet_cd, subnet)
+ return function(req, _)
+ local addr = req.qsource.dst_addr
+ if addr ~= nil and match_subnet(family, subnet_cd, bitlen, addr) then
+ return action
+ end
+ end
+end
+
+-- @function Module layers
+view.layer = {
+ begin = function(state, req)
+ -- Don't act on "finished" cases.
+ if bit.band(state, bit.bor(kres.FAIL, kres.DONE)) ~= 0 then return state end
+
+ evaluate(state, req)
+ return req.state
+ end
+}
+
+return view
diff --git a/modules/watchdog/.packaging/test.config b/modules/watchdog/.packaging/test.config
new file mode 100644
index 0000000..9d1a291
--- /dev/null
+++ b/modules/watchdog/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('watchdog')
+assert(watchdog)
+quit()
diff --git a/modules/watchdog/README.rst b/modules/watchdog/README.rst
new file mode 100644
index 0000000..514f6c0
--- /dev/null
+++ b/modules/watchdog/README.rst
@@ -0,0 +1,43 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-watchdog:
+
+Watchdog
+========
+
+This module cooperates with Systemd watchdog to restart the process in case
+the internal event loop gets stuck. The upstream Systemd unit files are configured
+to use this feature, which is turned on with the ``WatchdogSec=`` directive
+in the service file.
+
+As an optional feature, this module can also do an internal DNS query to check if resolver
+answers correctly. To use this feature you must configure DNS name and type to query for:
+
+.. code-block:: lua
+
+ watchdog.config({ qname = 'nic.cz.', qtype = kres.type.A })
+
+Each single query from watchdog must result in answer with
+RCODE = NOERROR or NXDOMAIN. Any other result will terminate the resolver
+(with SIGABRT) to allow the supervisor process to do cleanup, gather coredump
+and restart the resolver.
+
+It is recommended to use a name with a very short TTL to make sure the watchdog
+is testing all parts of resolver and not only its cache. Obviously this check
+makes sense only when used with very reliable domains; otherwise a failure
+on authoritative side will shutdown resolver!
+
+`WatchdogSec` specifies deadline for supervisor when the process will be killed.
+Watchdog queries are executed each `WatchdogSec / 2` seconds.
+This implies that **half** of `WatchdogSec` interval must be long enough for
+normal DNS query to succeed, so do not forget to add two or three seconds
+for random network timeouts etc.
+
+The module is loaded by default. If you'd like to disable it you can unload it:
+
+.. code-block:: lua
+
+ modules.unload('watchdog')
+
+Beware that unloading the module without disabling watchdog feature in supervisor
+will lead to infinite restart loop.
diff --git a/modules/watchdog/watchdog.lua b/modules/watchdog/watchdog.lua
new file mode 100644
index 0000000..6d50be2
--- /dev/null
+++ b/modules/watchdog/watchdog.lua
@@ -0,0 +1,129 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local ffi = require('ffi')
+
+ffi.cdef([[
+ int sd_watchdog_enabled(int unset_environment, uint64_t *usec);
+ int sd_notify(int unset_environment, const char *state);
+ void abort(void);
+]])
+
+local watchdog = {}
+local private = {}
+
+local function sd_signal_ok()
+ ffi.C.sd_notify(0, 'WATCHDOG=1')
+end
+
+function private.fail_callback()
+ log_error(ffi.C.LOG_GRP_WATCHDOG, 'ABORTING resolver, supervisor is expected to restart it')
+ ffi.C.abort()
+end
+
+-- logging
+local function add_tracer(logbuf)
+ return function (req)
+ local function qrylogger(_, msg)
+ jit.off(true, true) -- JIT for (C -> lua)^2 nesting isn't allowed
+ table.insert(logbuf, ffi.string(msg))
+ end
+ req.trace_log = ffi.cast('trace_log_f', qrylogger)
+ end
+end
+
+local function check_answer(logbuf)
+ return function (pkt, req)
+ req.trace_log:free()
+ if pkt ~= nil and (pkt:rcode() == kres.rcode.NOERROR
+ or pkt:rcode() == kres.rcode.NXDOMAIN) then
+ private.ok_callback()
+ return
+ end
+ log_info(ffi.C.LOG_GRP_WATCHDOG, 'watchdog query returned unexpected answer! query log:')
+ log_info(ffi.C.LOG_GRP_WATCHDOG, table.concat(logbuf, ''))
+ if pkt ~= nil then
+ log_info(ffi.C.LOG_GRP_WATCHDOG, 'problematic answer:\n%s', pkt)
+ else
+ log_info(ffi.C.LOG_GRP_WATCHDOG, 'answer was dropped')
+ end
+ -- failure! quit immediately to allow process supervisor to restart us
+ private.fail_callback()
+ end
+end
+private.check_answer_callback = check_answer
+
+local function timer()
+ local logbuf = {}
+ -- fire watchdog query
+ if private.qname and private.qtype then
+ log_info(ffi.C.LOG_GRP_WATCHDOG, 'starting watchdog query %s %s', private.qname, private.qtype)
+ resolve(private.qname,
+ private.qtype,
+ kres.class.IN,
+ {'TRACE'},
+ private.check_answer_callback(logbuf),
+ add_tracer(logbuf))
+ else
+ private.ok_callback()
+ end
+end
+
+function watchdog.config(cfg)
+ -- read only
+ if not cfg then
+ return private
+ end
+
+ local interval = tonumber(cfg.interval or private.interval or 10000)
+ if not interval or interval < 1 then
+ error('[watchdog] interval must be >= 1 ms')
+ end
+ private.interval = interval
+
+ -- qname = nil will disable DNS queries
+ private.qname = cfg.qname
+ private.qtype = cfg.qtype or kres.type.A
+
+ -- restart timers
+ watchdog.deinit()
+ private.event = event.recurrent(private.interval, timer)
+ return private
+end
+
+-- automatically enable watchdog if it is configured in systemd
+function watchdog.init()
+ if private.event then
+ error('[watchdog] module is already loaded')
+ end
+ local timeoutptr = ffi.new('uint64_t[1]')
+ local systemd_present, ret = pcall(function() return ffi.C.sd_watchdog_enabled(0, timeoutptr) end)
+ if not systemd_present then
+ log_info(ffi.C.LOG_GRP_WATCHDOG, 'systemd library not detected')
+ return
+ end
+ private.ok_callback = sd_signal_ok
+ if ret < 0 then
+ error('[watchdog] %s', ffi.string(ffi.C.knot_strerror(math.abs(ret))))
+ return
+ elseif ret == 0 then
+ log_info(ffi.C.LOG_GRP_WATCHDOG, 'disabled in systemd (WatchdogSec= not specified)')
+ return
+ end
+ local timeout = tonumber(timeoutptr[0]) / 1000 -- convert to ms
+ local interval = timeout / 2 -- halve interval to make sure we are never late
+ if interval < 1 then
+ log_error(ffi.C.LOG_GRP_WATCHDOG, 'error: WatchdogSec= must be at least 2ms! (got %d usec)',
+ tonumber(timeoutptr[0]))
+ end
+ watchdog.config({ interval = interval })
+ log_info(ffi.C.LOG_GRP_WATCHDOG, 'systemd watchdog enabled (check interval: %s ms, timeout: %s ms)',
+ private.interval, timeout)
+end
+
+function watchdog.deinit()
+ if private.event then
+ event.cancel(private.event)
+ private.event = nil
+ end
+end
+
+return watchdog
diff --git a/modules/workarounds/.packaging/test.config b/modules/workarounds/.packaging/test.config
new file mode 100644
index 0000000..c420810
--- /dev/null
+++ b/modules/workarounds/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('workarounds')
+assert(workarounds)
+quit()
diff --git a/modules/workarounds/README.rst b/modules/workarounds/README.rst
new file mode 100644
index 0000000..fcb04aa
--- /dev/null
+++ b/modules/workarounds/README.rst
@@ -0,0 +1,11 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-workarounds:
+
+Module `workarounds` resolver behavior on specific broken sub-domains.
+Currently it mainly disables case randomization.
+
+.. code-block:: lua
+
+ modules.load('workarounds < iterate')
+
diff --git a/modules/workarounds/workarounds.lua b/modules/workarounds/workarounds.lua
new file mode 100644
index 0000000..4ce7c47
--- /dev/null
+++ b/modules/workarounds/workarounds.lua
@@ -0,0 +1,23 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- Load dependent module
+if not policy then modules.load('policy') end
+
+local M = {} -- the module
+
+function M.config()
+ policy.add(policy.suffix(policy.FLAGS('NO_0X20'), {
+ -- https://github.com/DNS-OARC/dns-violations/blob/master/2017/DVE-2017-0003.md
+ todname('avqs.mcafee.com'), todname('avts.mcafee.com'),
+
+ -- https://github.com/DNS-OARC/dns-violations/blob/master/2017/DVE-2017-0006.md
+ -- Obtained via a reverse search on {ns1,ns3}.panthercdn.com.
+ todname('cdnga.com'), todname('cdngc.com'), todname('cdngd.com'),
+ todname('cdngl.com'), todname('cdngm.com'),
+ todname('cdngc.net'), todname('panthercdn.com'),
+
+ todname('magazine-fashion.net.'),
+ }))
+end
+
+return M
+