From bc180187655eedf72c1420a78c4c37523a067a19 Mon Sep 17 00:00:00 2001 From: Ben Bangert Date: Tue, 5 Jun 2018 17:16:30 -0700 Subject: [PATCH] feat: remove rust code Closes #1258 --- .travis.yml | 22 +- Cargo.lock | 2246 ------------------------- Cargo.toml | 2 - Dockerfile | 5 - autopush/tests/test_integration.py | 8 +- autopush/tests/test_rs_integration.py | 1555 ----------------- autopush_rs/Cargo.toml | 70 - autopush_rs/src/client.rs | 1054 ------------ autopush_rs/src/db/commands.rs | 416 ----- autopush_rs/src/db/macros.rs | 109 -- autopush_rs/src/db/mod.rs | 405 ----- autopush_rs/src/db/models.rs | 275 --- autopush_rs/src/db/util.rs | 15 - autopush_rs/src/errors.rs | 81 - autopush_rs/src/http.rs | 83 - autopush_rs/src/lib.rs | 129 -- autopush_rs/src/logging.rs | 50 - autopush_rs/src/main.rs | 53 - autopush_rs/src/protocol.rs | 158 -- autopush_rs/src/server/dispatch.rs | 92 - autopush_rs/src/server/metrics.rs | 23 - autopush_rs/src/server/mod.rs | 988 ----------- autopush_rs/src/server/tls.rs | 138 -- autopush_rs/src/server/webpush_io.rs | 66 - autopush_rs/src/settings.rs | 131 -- autopush_rs/src/util/megaphone.rs | 359 ---- autopush_rs/src/util/mod.rs | 42 - autopush_rs/src/util/rc.rs | 53 - autopush_rs/src/util/send_all.rs | 91 - autopush_rs/src/util/timing.rs | 18 - autopush_rs/src/util/user_agent.rs | 92 - setup.py | 21 - tox.ini | 2 +- 33 files changed, 8 insertions(+), 8844 deletions(-) delete mode 100644 Cargo.lock delete mode 100644 Cargo.toml delete mode 100644 autopush/tests/test_rs_integration.py delete mode 100644 autopush_rs/Cargo.toml delete mode 100644 autopush_rs/src/client.rs delete mode 100644 autopush_rs/src/db/commands.rs delete mode 100644 autopush_rs/src/db/macros.rs delete mode 100644 autopush_rs/src/db/mod.rs delete mode 100644 autopush_rs/src/db/models.rs delete mode 100644 autopush_rs/src/db/util.rs delete mode 100644 autopush_rs/src/errors.rs delete mode 100644 autopush_rs/src/http.rs delete mode 100644 autopush_rs/src/lib.rs delete mode 100644 autopush_rs/src/logging.rs delete mode 100644 autopush_rs/src/main.rs delete mode 100644 autopush_rs/src/protocol.rs delete mode 100644 autopush_rs/src/server/dispatch.rs delete mode 100644 autopush_rs/src/server/metrics.rs delete mode 100644 autopush_rs/src/server/mod.rs delete mode 100644 autopush_rs/src/server/tls.rs delete mode 100644 autopush_rs/src/server/webpush_io.rs delete mode 100644 autopush_rs/src/settings.rs delete mode 100644 autopush_rs/src/util/megaphone.rs delete mode 100644 autopush_rs/src/util/mod.rs delete mode 100644 autopush_rs/src/util/rc.rs delete mode 100644 autopush_rs/src/util/send_all.rs delete mode 100644 autopush_rs/src/util/timing.rs delete mode 100644 autopush_rs/src/util/user_agent.rs diff --git a/.travis.yml b/.travis.yml index 5de1288b..05b5d6ac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ language: python cache: directories: - - $HOME/.cargo - - autopush_rs/target - $HOME/.cache/pip sudo: required dist: trusty @@ -13,29 +11,15 @@ matrix: env: TOXENV=py27 DDB=true CODECOV=true - python: pypy env: TOXENV=pypy DDB=true CODECOV=true - - env: TOXENV=flake8 WITH_RUST=false + - env: TOXENV=flake8 - python: 3.6 - env: TOXENV=py36-mypy WITH_RUST=false + env: TOXENV=py36-mypy allow_failures: - - env: TOXENV=py36-mypy WITH_RUST=false + - env: TOXENV=py36-mypy install: - ${DDB:+make ddb} - pip install tox ${CODECOV:+codecov} -- | - if [ ${WITH_RUST:-true} != "false" ]; then - curl https://sh.rustup.rs | sh -s -- -y || travis_terminate 1; - export PATH=$PATH:$HOME/.cargo/bin - # Build here instead of within tox's pip install -e. It hides the output - # which can kill the job on slower travis runs - if [ ${WITH_RUST} == "release" ]; then - cargo build --manifest-path autopush_rs/Cargo.toml --release || \ - travis_terminate 1 - else - cargo build --manifest-path autopush_rs/Cargo.toml || \ - travis_terminate 1 - fi - fi script: - tox -v -- ${CODECOV:+--with-coverage --cover-xml --cover-package=autopush} after_success: diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 50bc63fe..00000000 --- a/Cargo.lock +++ /dev/null @@ -1,2246 +0,0 @@ -[[package]] -name = "adler32" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "aho-corasick" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "aho-corasick" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "arrayref" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "arrayvec" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "atty" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", - "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "autopush" -version = "0.1.0" -dependencies = [ - "base64 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "cadence 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", - "chan-signal 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "config 0.8.0 (git+https://github.com/mehcode/config-rs?rev=e8fa9fee96185ddd18ebcef8a925c75459111edb)", - "docopt 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "env_logger 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)", - "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "fernet 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-backoff 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "httparse 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.11.27 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "mozsvc-common 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)", - "rusoto_core 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rusoto_credential 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rusoto_dynamodb 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sentry 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_dynamodb 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", - "slog 2.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "slog-async 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "slog-mozlog-json 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "slog-scope 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "slog-stdlog 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "slog-term 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "state_machine_future 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-openssl 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-tungstenite 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tungstenite 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "woothee 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "backtrace" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "backtrace-sys 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-demangle 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "backtrace-sys" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "base64" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "bit-set" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "bit-vec" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "bitflags" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "bitflags" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "block-buffer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "build_const" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "byte-tools" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "byteorder" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "bytes" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "cadence" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "cc" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "cfg-if" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "chan" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "chan-signal" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "chan 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "chrono" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "num-integer 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "config" -version = "0.8.0" -source = "git+https://github.com/mehcode/config-rs?rev=e8fa9fee96185ddd18ebcef8a925c75459111edb#e8fa9fee96185ddd18ebcef8a925c75459111edb" -dependencies = [ - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)", - "serde-hjson 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", - "toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "yaml-rust 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "constant_time_eq" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "core-foundation" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "core-foundation-sys" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crc" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "crossbeam-deque" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-epoch 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-utils" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crypto-mac" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "darling" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "darling_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "darling_macro 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "darling_core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ident_case 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "darling_macro" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "darling_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "derive_state_machine_future" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "darling 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "petgraph 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "digest" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "docopt" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "dtoa" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "encoding_rs" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "env_logger" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", - "humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "termcolor 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "erased-serde" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "serde 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "error-chain" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "backtrace 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "error-chain" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "backtrace 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "fernet" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "base64 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fixedbitset" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "futures" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "futures-backoff" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-timer 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "futures-cpupool" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "futures-timer" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "generic-array" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "heck" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "hex" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "hmac" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crypto-mac 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "hostname" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", - "winutil 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "httparse" -version = "1.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "humantime" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "quick-error 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "hyper" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "base64 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "httparse 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "mime 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "relay 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-proto 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "want 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "hyper-tls" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.11.27 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ident_case" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "idna" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "input_buffer" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "iovec" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "isatty" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "itoa" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "language-tags" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "lazy_static" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "lazy_static" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "lazycell" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "libc" -version = "0.2.41" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "libflate" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "linked-hash-map" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_test 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "linked-hash-map" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "log" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "log" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "matches" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "memchr" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "memchr" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "memchr" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "memoffset" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "mime" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "mime_guess" -version = "2.0.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "mime 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "phf 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", - "phf_codegen 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", - "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "mio" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazycell 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "miow" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "mozsvc-common" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "native-tls" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)", - "schannel 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", - "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "net2" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "nodrop" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "nom" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "num-integer" -version = "0.1.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "num-traits" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "num-traits" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "num_cpus" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "openssl" -version = "0.9.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.31 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "openssl" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.31 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "openssl-sys" -version = "0.9.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", - "vcpkg 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ordermap" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "percent-encoding" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "petgraph" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "ordermap 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "phf" -version = "0.7.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "phf_codegen" -version = "0.7.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "phf_generator 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", - "phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "phf_generator" -version = "0.7.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "phf_shared" -version = "0.7.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "pkg-config" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "proc-macro2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "quick-error" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "quote" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "quote" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand" -version = "0.3.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "redox_syscall" -version = "0.1.38" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "redox_termios" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "redox_syscall 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "regex" -version = "0.1.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", - "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "regex" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "regex" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "regex-syntax" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "regex-syntax" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "regex-syntax" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "relay" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "remove_dir_all" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rent_to_own" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "reqwest" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "encoding_rs 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.11.27 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper-tls 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libflate 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "mime_guess 2.0.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_urlencoded 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "uuid 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rusoto_core" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "base64 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "hmac 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.11.27 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper-tls 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rusoto_credential 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)", - "sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "xml-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rusoto_credential" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.11.27 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper-tls 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rusoto_dynamodb" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.11.27 (registry+https://github.com/rust-lang/crates.io-index)", - "rusoto_core 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "rustc_version" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "safemem" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "schannel" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "scoped-tls" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "scopeguard" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "security-framework" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "security-framework-sys" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "sentry" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "backtrace 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", - "error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.11.27 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper-tls 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "serde" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "serde" -version = "1.0.63" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "serde-hjson" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "serde_derive" -version = "1.0.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "serde_dynamodb" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rusoto_dynamodb 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "serde_json" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "serde_test" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "serde_urlencoded" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "sha1" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "sha2" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "siphasher" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "slab" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "slab" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "slog" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "erased-serde 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "slog-async" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "slog 2.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "slog-mozlog-json" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", - "slog 2.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "slog-scope" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "slog 2.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "slog-stdlog" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "slog 2.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "slog-scope 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "slog-term" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "isatty 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "slog 2.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "term 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "smallvec" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "state_machine_future" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "derive_state_machine_future 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "rent_to_own 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "strsim" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "syn" -version = "0.11.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "syn" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "synom" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "take" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "take_mut" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "tempdir" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "term" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "termcolor" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "wincolor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "termion" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "thread-id" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "thread_local" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "thread_local" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "time" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-fs 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-tcp 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-threadpool 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-timer 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-udp 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-core" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)", - "scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-timer 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-executor" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-fs" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-threadpool 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-io" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-openssl" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-proto" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-reactor" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-service" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-tcp" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-threadpool" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-timer" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-tls" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tungstenite 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-udp" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "toml" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "serde 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "try-lock" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "tungstenite" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "base64 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "httparse 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "input_buffer 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "sha1 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "utf-8 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "typenum" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "ucd-util" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "unicase" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "unicase" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "unicode-normalization" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "unicode-segmentation" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "unicode-xid" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "unicode-xid" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "unreachable" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "url" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "utf-8" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "utf8-ranges" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "utf8-ranges" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "uuid" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "uuid" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "vcpkg" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "version_check" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "want" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "try-lock 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "wincolor" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "winutil" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "woothee" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "xml-rs" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "yaml-rust" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[metadata] -"checksum adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6cbd0b9af8587c72beadc9f72d35b9fbb070982c9e6203e46e93f10df25f8f45" -"checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" -"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" -"checksum arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0fd1479b7c29641adbd35ff3b5c293922d696a92f25c8c975da3e0acbc87258f" -"checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" -"checksum atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2fc4a1aa4c24c0718a250f0681885c1af91419d242f29eb8f2ab28502d80dbd1" -"checksum backtrace 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "dbdd17cd962b570302f5297aea8648d5923e22e555c2ed2d8b2e34eca646bf6d" -"checksum backtrace-sys 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "3951a5d75651d5817056daf3132ed9cd7918bc7f2afc313b379009e832209a4d" -"checksum base64 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9263aa6a38da271eec5c91a83ce1e800f093c8535788d403d626d8d5c3f8f007" -"checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c" -"checksum bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f" -"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" -"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789" -"checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab" -"checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" -"checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" -"checksum byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "74c0b906e9446b0a2e4f760cdb3fa4b2c48cdc6db8766a845c54b6ff063fd2e9" -"checksum bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7dd32989a66957d3f0cba6588f15d4281a733f4e9ffc43fcd2385f57d3bf99ff" -"checksum cadence 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f844988bbe2f2a0c67b9982d1788ba800bb1b2910a69141247c94e5667a4a0a0" -"checksum cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "0ebb87d1116151416c0cf66a0e3fb6430cccd120fd6300794b4dfaa050ac40ba" -"checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18" -"checksum chan 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "9af7c487bb99c929ba2715b1a3a7bf45f5062bf5b6eae5d32b292a96c5865172" -"checksum chan-signal 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f1f1e11f6e1c14c9e805a87c622cb8fcb636283b3119a2150af390cc6702d7fe" -"checksum chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1cce36c92cb605414e9b824f866f5babe0a0368e39ea07393b9b63cf3844c0e6" -"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum config 0.8.0 (git+https://github.com/mehcode/config-rs?rev=e8fa9fee96185ddd18ebcef8a925c75459111edb)" = "" -"checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" -"checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67" -"checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d" -"checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" -"checksum crossbeam 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "bd66663db5a988098a89599d4857919b3acf7f61402e61365acfd3919857b9be" -"checksum crossbeam-deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fe8153ef04a7594ded05b427ffad46ddeaf22e63fd48d42b3e1e3bb4db07cae7" -"checksum crossbeam-epoch 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9b4e2817eb773f770dcb294127c011e22771899c21d18fce7dd739c0b9832e81" -"checksum crossbeam-utils 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d636a8b3bcc1b409d7ffd3facef8f21dcb4009626adbd0c5e6c4305c07253c7b" -"checksum crypto-mac 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0999b4ff4d3446d4ddb19a63e9e00c1876e75cd7000d20e57a693b4b3f08d958" -"checksum darling 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1630fdbe3554154a50624487c79b0140a424e87dc08061db1a2211359792acab" -"checksum darling_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d12d2eeb837786ace70b6bca9adfeaef4352cc68d6a42e8e3d0c4159bbca7ab2" -"checksum darling_macro 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "01581bdeabb86f69970dbd9e6ee3c61963f9a7321169589e3dffa16033c0928c" -"checksum derive_state_machine_future 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "54e84dd4e2e6b94edda02aaae8fd8d02f68404817c89183e16d217bb380d08e8" -"checksum digest 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "00a49051fef47a72c9623101b19bd71924a45cca838826caae3eaa4d00772603" -"checksum docopt 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e67fb750c36fc6fffbd3575cf8f2b46790fc0b05096ae3c03a36cf71b55e1e2b" -"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab" -"checksum encoding_rs 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98fd0f24d1fb71a4a6b9330c8ca04cbd4e7cc5d846b54ca74ff376bc7c9f798d" -"checksum env_logger 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0e6e40ebb0e66918a37b38c7acab4e10d299e0463fe2af5d29b9cc86710cfd2a" -"checksum erased-serde 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c564e32677839f1c551664c478e079c9b128a1a2d223180bffb2ddfabeded0be" -"checksum error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8" -"checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3" -"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" -"checksum fernet 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b001fae1d5ef9a63cb117462e0c6d76eca42891db7d6140ed12e8b1792277028" -"checksum fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" -"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "1a70b146671de62ec8c8ed572219ca5d594d9b06c0b364d5e67b722fc559b48c" -"checksum futures-backoff 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "79f590345ee8cedf1f36068db74ca81c7e7bf753a3e59ea58b0826243a13971b" -"checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" -"checksum futures-timer 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a5cedfe9b6dc756220782cc1ba5bcb1fa091cdcba155e40d3556159c3db58043" -"checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" -"checksum heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea04fa3ead4e05e51a7c806fc07271fdbde4e246a6c6d1efd52e72230b771b82" -"checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" -"checksum hmac 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44f3bdb08579d99d7dc761c0e266f13b5f2ab8c8c703b9fc9ef333cd8f48f55e" -"checksum hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e" -"checksum httparse 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2f407128745b78abc95c0ffbe4e5d37427fdc0d45470710cfef8c44522a2e37" -"checksum humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0484fda3e7007f2a4a0d9c3a703ca38c71c54c55602ce4660c419fd32e188c9e" -"checksum hyper 0.11.27 (registry+https://github.com/rust-lang/crates.io-index)" = "34a590ca09d341e94cddf8e5af0bbccde205d5fbc2fa3c09dd67c7f85cea59d7" -"checksum hyper-tls 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a5aa51f6ae9842239b0fac14af5f22123b8432b4cc774a44ff059fcba0f675ca" -"checksum ident_case 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c9826188e666f2ed92071d2dadef6edc430b11b158b5b2b3f4babbcc891eaaa" -"checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d" -"checksum input_buffer 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "64fc52dd2f15e7ce28663e4eada58f457aa8c220044d531c3b8d56a8781af9b1" -"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" -"checksum isatty 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6c324313540cd4d7ba008d43dc6606a32a5579f13cc17b2804c13096f0a5c522" -"checksum itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c069bbec61e1ca5a596166e55dfe4773ff745c3d16b700013bcaff9a6df2c682" -"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" -"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" -"checksum lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e6412c5e2ad9584b0b8e979393122026cdd6d2a80b933f890dcd694ddbe73739" -"checksum lazycell 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a6f08839bc70ef4a3fe1d566d5350f519c5912ea86be0df1740a7d247c7fc0ef" -"checksum libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)" = "ac8ebf8343a981e2fa97042b14768f02ed3e1d602eac06cae6166df3c8ced206" -"checksum libflate 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "1a429b86418868c7ea91ee50e9170683f47fd9d94f5375438ec86ec3adb74e8e" -"checksum linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" -"checksum linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70fb39025bc7cdd76305867c4eccf2f2dcf6e9a57f5b21a93e1c2d86cd03ec9e" -"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" -"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2" -"checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" -"checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" -"checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" -"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" -"checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" -"checksum mime 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0b28683d0b09bbc20be1c9b3f6f24854efb1356ffcffee08ea3f6e65596e85fa" -"checksum mime_guess 2.0.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130ea3c9c1b65dba905ab5a4d9ac59234a9585c24d135f264e187fe7336febbd" -"checksum mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)" = "6d771e3ef92d58a8da8df7d6976bfca9371ed1de6619d9d5a5ce5b1f29b85bfe" -"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" -"checksum mozsvc-common 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "732386d576b01d7e011eedcf3d6373322ee4699bf5c46585ef416959a7f567fa" -"checksum native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f74dbadc8b43df7864539cedb7bc91345e532fdd913cfdc23ad94f4d2d40fbc0" -"checksum net2 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)" = "9044faf1413a1057267be51b5afba8eb1090bd2231c693664aa1db716fe1eae0" -"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" -"checksum nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b" -"checksum num-integer 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "6ac0ea58d64a89d9d6b7688031b3be9358d6c919badcf7fbb0527ccfd891ee45" -"checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" -"checksum num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "775393e285254d2f5004596d69bb8bc1149754570dcc08cf30cabeba67955e28" -"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" -"checksum openssl 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)" = "736898acffb0e00a14d86c5b836aee2ca1c502efcf1c1b0d17a936dfc49ec47f" -"checksum openssl 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)" = "a3605c298474a3aa69de92d21139fb5e2a81688d308262359d85cdd0d12a7985" -"checksum openssl-sys 0.9.31 (registry+https://github.com/rust-lang/crates.io-index)" = "a4d6a27d108b29befe1822d40e2e22f85518dac59acbf7f30fdc532f48fd0a77" -"checksum ordermap 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a86ed3f5f244b372d6b1a00b72ef7f8876d0bc6a78a4c9985c53614041512063" -"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" -"checksum petgraph 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "8b30dc85588cd02b9b76f5e386535db546d21dc68506cff2abebee0b6445e8e4" -"checksum phf 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "7d37a244c75a9748e049225155f56dbcb98fe71b192fd25fd23cb914b5ad62f2" -"checksum phf_codegen 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "4e4048fe7dd7a06b8127ecd6d3803149126e9b33c7558879846da3a63f734f2b" -"checksum phf_generator 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "05a079dd052e7b674d21cb31cbb6c05efd56a2cd2827db7692e2f1a507ebd998" -"checksum phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "c2261d544c2bb6aa3b10022b0be371b9c7c64f762ef28c6f5d4f1ef6d97b5930" -"checksum pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "110d5ee3593dbb73f56294327fe5668bcc997897097cbc76b51e7aed3f52452f" -"checksum proc-macro2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a45f2f0ae0b5757f6fe9e68745ba25f5246aea3598984ed81d013865873c1f84" -"checksum quick-error 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eda5fe9b71976e62bc81b781206aaa076401769b2143379d3eb2118388babac4" -"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" -"checksum quote 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e53eeda07ddbd8b057dde66d9beded11d0dfda13f0db0769e6b71d6bcf2074e" -"checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1" -"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" -"checksum rand 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a89abf8d34faf9783692392dca7bcdc6e82fa84eca86ccb6301ec87f3497185" -"checksum rand_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1b7a5f27547c49e5ccf8a586db3f3782fd93cf849780b21853b9d981db203302" -"checksum redox_syscall 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "0a12d51a5b5fd700e6c757f15877685bfa04fd7eb60c108f01d045cafa0073c2" -"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" -"checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" -"checksum regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384" -"checksum regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75ecf88252dce580404a22444fc7d626c01815debba56a7f4f536772a5ff19d3" -"checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" -"checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" -"checksum regex-syntax 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8f1ac0f60d675cc6cf13a20ec076568254472551051ad5dd050364d70671bf6b" -"checksum relay 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1576e382688d7e9deecea24417e350d3062d97e32e45d70b1cde65994ff1489a" -"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" -"checksum rent_to_own 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05a51ad2b1c5c710fa89e6b1631068dab84ed687bc6a5fe061ad65da3d0c25b2" -"checksum reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)" = "241faa9a8ca28a03cbbb9815a5d085f271d4c0168a19181f106aa93240c22ddb" -"checksum rusoto_core 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "12daaa6d62d64f6447bf0299ce775f4e05f8e75e5418e817da094b9de04ad22d" -"checksum rusoto_credential 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "53199d09fd1b7d4f5ac50f4d23106577624238ea77cae2b44eb1d1fc4cd956a4" -"checksum rusoto_dynamodb 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "221fb3362d86a9e6a064cf5f71044cb1cc67a43e7d151008d9ca2a899104c39a" -"checksum rustc-demangle 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "76d7ba1feafada44f2d38eed812bd2489a03c0f5abb975799251518b68848649" -"checksum rustc_version 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a54aa04a10c68c1c4eacb4337fd883b435997ede17a9385784b990777686b09a" -"checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" -"checksum schannel 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "85fd9df495640643ad2d00443b3d78aae69802ad488debab4f1dd52fc1806ade" -"checksum scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28" -"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" -"checksum security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "dfa44ee9c54ce5eecc9de7d5acbad112ee58755239381f687e564004ba4a2332" -"checksum security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "5421621e836278a0b139268f36eee0dc7e389b784dc3f79d8f11aabadf41bead" -"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum sentry 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4c931969579f133c35280ccc1969a4786984449bd8adad937ef9f76cef3bdfbc" -"checksum serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" -"checksum serde 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)" = "6f4a07014dd9a6845448a9e62f6f27595847f09828caabf1b1d50bb6755fa4d2" -"checksum serde-hjson 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a2376ebb8976138927f48b49588ef73cde2f6591b8b3df22f4063e0f27b9bec" -"checksum serde_derive 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)" = "d68003d21ef20c5c48354638c2e5c0d4ce4a056fdbf973839e0e86054eae7794" -"checksum serde_dynamodb 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe982f1146e7134af153b2d1fdcab083f09c184600b232cd7a120ec191a4e1b" -"checksum serde_json 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "ee382a792fabc5d720630aeafe1a4c69abe3d32aaaa5dbba6762fd8246d1bbe3" -"checksum serde_test 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "110b3dbdf8607ec493c22d5d947753282f3bae73c0f56d322af1e8c78e4c23d5" -"checksum serde_urlencoded 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e703cef904312097cfceab9ce131ff6bbe09e8c964a0703345a5f49238757bc1" -"checksum sha1 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "933ed2cffa70bb0e1a2c1bf1174d0f39dd3b81bbf5597d882d886710c8729924" -"checksum sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9eb6be24e4c23a84d7184280d2722f7f2731fcdd4a9d886efbfe4413e4847ea0" -"checksum siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0df90a788073e8d0235a67e50441d47db7c8ad9debd91cbf43736a2a92d36537" -"checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" -"checksum slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fdeff4cd9ecff59ec7e3744cbca73dfe5ac35c2aedb2cfba8a1c715a18912e9d" -"checksum slog 2.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2f7bfce6405155042d42ec0e645efe43eddedd7be280063ce0623b120014e7f9" -"checksum slog-async 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e544d16c6b230d84c866662fe55e31aacfca6ae71e6fc49ae9a311cb379bfc2f" -"checksum slog-mozlog-json 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f400f1c5db96f1f52065e8931ca0c524cceb029f7537c9e6d5424488ca137ca0" -"checksum slog-scope 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "053344c94c0e2b22da6305efddb698d7c485809427cf40555dc936085f67a9df" -"checksum slog-stdlog 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ac42f8254ae996cc7d640f9410d3b048dcdf8887a10df4d5d4c44966de24c4a8" -"checksum slog-term 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5951a808c40f419922ee014c15b6ae1cd34d963538b57d8a4778b9ca3fff1e0b" -"checksum smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c8cbcd6df1e117c2210e13ab5109635ad68a929fcbb8964dc965b76cb5ee013" -"checksum state_machine_future 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eaafbb574dda413e09727f3a534af6837756c9edb69691c120a3240fa30179da" -"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" -"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" -"checksum syn 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "99d991a9e7c33123925e511baab68f7ec25c3795962fe326a2395e5a42a614f0" -"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" -"checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5" -"checksum take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" -"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" -"checksum term 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5e6b677dd1e8214ea1ef4297f85dbcbed8e8cdddb561040cc998ca2551c37561" -"checksum termcolor 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "adc4587ead41bf016f11af03e55a624c06568b5a19db4e90fde573d805074f83" -"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" -"checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" -"checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" -"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963" -"checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b" -"checksum tokio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d00555353b013e170ed8bc4e13f648a317d1fd12157dbcae13f7013f6cf29f5" -"checksum tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "aeeffbbb94209023feaef3c196a41cbcdafa06b4a6f893f68779bb5e53796f71" -"checksum tokio-executor 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8cac2a7883ff3567e9d66bb09100d09b33d90311feca0206c7ca034bc0c55113" -"checksum tokio-fs 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "76766830bbf9a2d5bfb50c95350d56a2e79e2c80f675967fff448bc615899708" -"checksum tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "6af9eb326f64b2d6b68438e1953341e00ab3cf54de7e35d92bfc73af8555313a" -"checksum tokio-openssl 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7e88cd8a247335be936e713ca68a1cb5227df649e22e975b9a71b4e862169e82" -"checksum tokio-proto 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fbb47ae81353c63c487030659494b295f6cb6576242f907f203473b191b0389" -"checksum tokio-reactor 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3cedc8e5af5131dc3423ffa4f877cce78ad25259a9a62de0613735a13ebc64b" -"checksum tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162" -"checksum tokio-tcp 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ec9b094851aadd2caf83ba3ad8e8c4ce65a42104f7b94d9e6550023f0407853f" -"checksum tokio-threadpool 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5783254b10c7c84a56f62c74766ef7e5b83d1f13053218c7cab8d3f2c826fa0e" -"checksum tokio-timer 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535fed0ccee189f3d48447587697ba3fd234b3dbbb091f0ec4613ddfec0a7c4c" -"checksum tokio-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "772f4b04e560117fe3b0a53e490c16ddc8ba6ec437015d91fa385564996ed913" -"checksum tokio-tungstenite 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3cedf5e2d459171cb08aa6126572a06d827de4208d35281a4cc98081182d5d1a" -"checksum tokio-udp 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "137bda266504893ac4774e0ec4c2108f7ccdbcb7ac8dced6305fe9e4e0b5041a" -"checksum toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a0263c6c02c4db6c8f7681f9fd35e90de799ebd4cfdeab77a38f4ff6b3d8c0d9" -"checksum try-lock 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee2aa4715743892880f70885373966c83d73ef1b0838a664ef0c76fffd35e7c2" -"checksum tungstenite 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b3904357c86319d331cf9430bc7379a669f1bde1e20be51115c0fc96c0b9c9de" -"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" -"checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d" -"checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" -"checksum unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284b6d3db520d67fbe88fd778c21510d1b0ba4a551e5d0fbb023d33405f6de8a" -"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -"checksum unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6a0180bc61fc5a987082bfa111f4cc95c4caff7f9799f3e46df09163a937aa25" -"checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" -"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" -"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" -"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" -"checksum url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f808aadd8cfec6ef90e4a14eb46f24511824d1ac596b9682703c87056c8678b7" -"checksum utf-8 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1262dfab4c30d5cb7c07026be00ee343a6cf5027fdc0104a9160f354e5db75c" -"checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" -"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" -"checksum uuid 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22" -"checksum uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e1436e58182935dcd9ce0add9ea0b558e8a87befe01c1a301e6020aeb0876363" -"checksum vcpkg 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7ed0f6789c8a85ca41bbc1c9d175422116a9869bd1cf31bb08e1493ecce60380" -"checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d" -"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -"checksum want 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a05d9d966753fa4b5c8db73fcab5eed4549cfe0e1e4e66911e5564a0085c35d1" -"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" -"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" -"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" -"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -"checksum wincolor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eeb06499a3a4d44302791052df005d5232b927ed1a9658146d842165c4de7767" -"checksum winutil 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7daf138b6b14196e3830a588acf1e86966c694d3e8fb026fb105b8b5dca07e6e" -"checksum woothee 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e7c2cece51be2a2f25518a9efdd303d5ca8dfa619272f091e7dedbba95d1873" -"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -"checksum xml-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c1cb601d29fe2c2ac60a2b2e5e293994d87a1f6fa9687a31a15270f909be9c2" -"checksum yaml-rust 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57ab38ee1a4a266ed033496cf9af1828d8d6e6c1cfa5f643a2809effcae4d628" diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 422eafe7..00000000 --- a/Cargo.toml +++ /dev/null @@ -1,2 +0,0 @@ -[workspace] -members = ["autopush_rs"] diff --git a/Dockerfile b/Dockerfile index 84aab020..de456d6c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,16 +8,11 @@ WORKDIR /app ENV PATH=$PATH:/root/.cargo/bin RUN \ - export WITH_RUST=release && \ apt-get update && \ apt-get install -y -qq libexpat1-dev gcc libssl-dev libffi-dev libjemalloc1 && \ - curl https://sh.rustup.rs | sh -s -- -y && \ make clean && \ pip install -r requirements.txt && \ pypy setup.py develop && \ - cargo install --path autopush_rs && \ - cargo clean && \ - rustup self uninstall -y ENTRYPOINT ["/app/entrypoint.sh"] CMD ["autopush"] diff --git a/autopush/tests/test_integration.py b/autopush/tests/test_integration.py index 09eb05f2..e9bb79e3 100644 --- a/autopush/tests/test_integration.py +++ b/autopush/tests/test_integration.py @@ -102,7 +102,7 @@ def __getattribute__(self, name): def connect(self, connection_port=None): url = self.url - if connection_port: + if connection_port: # pragma: nocover url = "ws://localhost:{}/".format(connection_port) self.ws = websocket.create_connection(url) return self.ws.connected @@ -117,7 +117,7 @@ def hello(self, uaid=None, services=None): channelIDs=chans) if uaid or self.uaid: hello_dict["uaid"] = uaid or self.uaid - if services: + if services: # pragma: nocover hello_dict["broadcasts"] = services msg = json.dumps(hello_dict) log.debug("Send: %s", msg) @@ -133,7 +133,7 @@ def hello(self, uaid=None, services=None): self.uaid = result["uaid"] return result - def broadcast_subscribe(self, services): + def broadcast_subscribe(self, services): # pragma: nocover msg = json.dumps(dict(messageType="broadcast_subscribe", broadcasts=services)) log.debug("Send: %s", msg) @@ -259,7 +259,7 @@ def get_notification(self, timeout=1): finally: self.ws.settimeout(orig_timeout) - def get_broadcast(self, timeout=1): + def get_broadcast(self, timeout=1): # pragma: nocover orig_timeout = self.ws.gettimeout() self.ws.settimeout(timeout) try: diff --git a/autopush/tests/test_rs_integration.py b/autopush/tests/test_rs_integration.py deleted file mode 100644 index 6dc6e5f7..00000000 --- a/autopush/tests/test_rs_integration.py +++ /dev/null @@ -1,1555 +0,0 @@ -"""Rust Connection Node Integration Test - -Differences from original integration test: - -1. Connection node metrics can't be counted from the Python side. -2. Increment is only run after all messages are ack'd, rather than merely the - last message as production currently uses. - -""" -import json -import logging -import os -import re -import signal -import socket -import subprocess -import time -import uuid -from contextlib import contextmanager -from http.server import BaseHTTPRequestHandler, HTTPServer -from threading import Thread, Event -from queue import Queue, Empty -from unittest.case import SkipTest - -import ecdsa -import psutil -import requests -import twisted.internet.base -from cryptography.fernet import Fernet -from twisted.internet.defer import inlineCallbacks, returnValue -from twisted.internet.threads import deferToThread -from twisted.trial import unittest -from twisted.logger import globalLogPublisher - -import autopush.tests -from autopush.config import AutopushConfig -from autopush.db import ( - get_month, - has_connected_this_month, - Message, -) -from autopush.logging import begin_or_register -from autopush.main import ( - ConnectionApplication, - EndpointApplication, -) -from autopush.utils import base64url_encode -from autopush.tests.support import TestingLogObserver -from autopush.tests.test_integration import ( - Client, - _get_vapid, -) - -log = logging.getLogger(__name__) - -here_dir = os.path.abspath(os.path.dirname(__file__)) -root_dir = os.path.dirname(os.path.dirname(here_dir)) - -twisted.internet.base.DelayedCall.debug = True - -ROUTER_TABLE = os.environ.get("ROUTER_TABLE", "router_int_test") -MESSAGE_TABLE = os.environ.get("MESSAGE_TABLE", "message_int_test") - -CRYPTO_KEY = Fernet.generate_key() -CONNECTION_PORT = 9150 -ENDPOINT_PORT = 9160 -ROUTER_PORT = 9170 -MP_CONNECTION_PORT = 9052 -MP_ROUTER_PORT = 9072 -RP_CONNECTION_PORT = 9054 -RP_ROUTER_PORT = 9074 - -CN_SERVER = None -CN_MP_SERVER = None -MOCK_SERVER_THREAD = None -CN_QUEUES = [] - - -def get_free_port(): - s = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM) - s.bind(('localhost', 0)) - address, port = s.getsockname() - s.close() - return port - - -MOCK_MP_SERVER_PORT = get_free_port() - - -def enqueue_output(out, queue): - for line in iter(out.readline, b''): - queue.put(line) - out.close() - - -def setup_module(): - global CN_SERVER, CN_QUEUES, CN_MP_SERVER, MOCK_SERVER_THREAD - logging.getLogger('boto').setLevel(logging.CRITICAL) - if "SKIP_INTEGRATION" in os.environ: # pragma: nocover - raise SkipTest("Skipping integration tests") - - conn_conf = dict( - hostname='localhost', - port=CONNECTION_PORT, - endpoint_hostname="localhost", - endpoint_port=ENDPOINT_PORT, - router_port=ROUTER_PORT, - endpoint_scheme='http', - statsd_host="", - router_tablename=ROUTER_TABLE, - message_tablename=MESSAGE_TABLE, - crypto_key=CRYPTO_KEY, - auto_ping_interval=60.0, - auto_ping_timeout=10.0, - close_handshake_timeout=5, - max_connections=5000, - human_logs="true", - ) - rust_bin = root_dir + "/target/release/autopush_rs" - possible_paths = ["/target/debug/autopush_rs", - "/autopush_rs/target/release/autopush_rs", - "/autopush_rs/target/debug/autopush_rs"] - while possible_paths and not os.path.exists(rust_bin): # pragma: nocover - rust_bin = root_dir + possible_paths.pop(0) - - # Setup the environment - for key, val in conn_conf.items(): - key = "autopush_" + key - os.environ[key.upper()] = str(val) - - cmd = [rust_bin] - CN_SERVER = subprocess.Popen(cmd, shell=True, env=os.environ, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - universal_newlines=True) - # Spin up the readers to dump the output from stdout/stderr - out_q = Queue() - t = Thread(target=enqueue_output, args=(CN_SERVER.stdout, out_q)) - t.daemon = True # thread dies with the program - t.start() - err_q = Queue() - t = Thread(target=enqueue_output, args=(CN_SERVER.stderr, err_q)) - t.daemon = True # thread dies with the program - t.start() - CN_QUEUES.extend([out_q, err_q]) - - # Megaphone API mock - MockMegaphoneRequestHandler.services = {} - MockMegaphoneRequestHandler.polled.clear() - mock_server = HTTPServer(('localhost', MOCK_MP_SERVER_PORT), - MockMegaphoneRequestHandler) - MOCK_SERVER_THREAD = Thread(target=mock_server.serve_forever) - MOCK_SERVER_THREAD.setDaemon(True) - MOCK_SERVER_THREAD.start() - - # Setup the megaphone connection node - megaphone_api_url = 'http://localhost:{port}/v1/broadcasts'.format( - port=MOCK_MP_SERVER_PORT) - conn_conf.update(dict( - port=MP_CONNECTION_PORT, - endpoint_port=ENDPOINT_PORT, - router_port=MP_ROUTER_PORT, - auto_ping_interval=0.5, - auto_ping_timeout=10.0, - close_handshake_timeout=5, - max_connections=5000, - megaphone_api_url=megaphone_api_url, - megaphone_api_token=MockMegaphoneRequestHandler.token, - megaphone_poll_interval=1, - )) - - # Setup the environment - for key, val in conn_conf.items(): - key = "autopush_" + key - os.environ[key.upper()] = str(val) - - cmd = [rust_bin] - CN_MP_SERVER = subprocess.Popen(cmd, shell=True, env=os.environ) - time.sleep(1) - - -def teardown_module(): - global CN_SERVER, CN_MP_SERVER - # This kinda sucks, but its the only way to nuke the child procs - proc = psutil.Process(pid=CN_SERVER.pid) - child_procs = proc.children(recursive=True) - for p in [proc] + child_procs: - os.kill(p.pid, signal.SIGTERM) - CN_SERVER.wait() - - proc = psutil.Process(pid=CN_MP_SERVER.pid) - child_procs = proc.children(recursive=True) - for p in [proc] + child_procs: - os.kill(p.pid, signal.SIGTERM) - CN_MP_SERVER.wait() - - -class MockMegaphoneRequestHandler(BaseHTTPRequestHandler): - API_PATTERN = re.compile(r'/v1/broadcasts') - services = {} - polled = Event() - token = "Bearer {}".format(uuid.uuid4().hex) - - def do_GET(self): - if re.search(self.API_PATTERN, self.path): - assert self.headers.getheader("Authorization") == self.token - self.send_response(requests.codes.ok) - self.send_header('Content-Type', 'application/json; charset=utf-8') - self.end_headers() - response_content = json.dumps( - {"broadcasts": self.services} - ) - self.wfile.write(response_content.encode('utf-8')) - self.polled.set() - return - - -class TestRustWebPush(unittest.TestCase): - _endpoint_defaults = dict( - hostname='localhost', - port=ENDPOINT_PORT, - endpoint_port=ENDPOINT_PORT, - endpoint_scheme='http', - router_port=ROUTER_PORT, - statsd_host=None, - router_table=dict(tablename=ROUTER_TABLE), - message_table=dict(tablename=MESSAGE_TABLE), - use_cryptography=True, - ) - - def start_ep(self, ep_conf): - # Endpoint HTTP router - self.ep = ep = EndpointApplication( - ep_conf, - resource=autopush.tests.boto_resource - ) - ep.setup(rotate_tables=False) - ep.startService() - self.addCleanup(ep.stopService) - - def setUp(self): - self.logs = TestingLogObserver() - begin_or_register(self.logs) - self.addCleanup(globalLogPublisher.removeObserver, self.logs) - - self._ep_conf = AutopushConfig( - crypto_key=CRYPTO_KEY, - **self.endpoint_kwargs() - ) - self.start_ep(self._ep_conf) - - def tearDown(self): - for queue in CN_QUEUES: - is_empty = False - while not is_empty: - try: - line = queue.get_nowait() - except Empty: - is_empty = True - else: - print(line) - - def endpoint_kwargs(self): - return self._endpoint_defaults - - @inlineCallbacks - def quick_register(self, sslcontext=None): - client = Client("ws://localhost:{}/".format(CONNECTION_PORT), - sslcontext=sslcontext) - yield client.connect() - yield client.hello() - yield client.register() - returnValue(client) - - @inlineCallbacks - def shut_down(self, client=None): - if client: - yield client.disconnect() - - @contextmanager - def legacy_endpoint(self): - self.ep.conf._notification_legacy = True - yield - self.ep.conf._notification_legacy = False - - @property - def _ws_url(self): - return "ws://localhost:{}/".format(CONNECTION_PORT) - - @inlineCallbacks - def test_hello_echo(self): - client = Client(self._ws_url) - yield client.connect() - result = yield client.hello() - assert result != {} - assert result["use_webpush"] is True - yield self.shut_down(client) - - @inlineCallbacks - def test_hello_with_bad_prior_uaid(self): - non_uaid = uuid.uuid4().hex - client = Client(self._ws_url) - yield client.connect() - result = yield client.hello(uaid=non_uaid) - assert result != {} - assert result["uaid"] != non_uaid - assert result["use_webpush"] is True - yield self.shut_down(client) - - @inlineCallbacks - def test_basic_delivery(self): - data = str(uuid.uuid4()) - client = yield self.quick_register() - result = yield client.send_notification(data=data) - assert result["headers"]["encryption"] == client._crypto_key - assert result["data"] == base64url_encode(data) - assert result["messageType"] == "notification" - yield self.shut_down(client) - - @inlineCallbacks - def test_topic_basic_delivery(self): - data = str(uuid.uuid4()) - client = yield self.quick_register() - result = yield client.send_notification(data=data, topic="Inbox") - assert result["headers"]["encryption"] == client._crypto_key - assert result["data"] == base64url_encode(data) - assert result["messageType"] == "notification" - yield self.shut_down(client) - - @inlineCallbacks - def test_topic_replacement_delivery(self): - data = str(uuid.uuid4()) - data2 = str(uuid.uuid4()) - client = yield self.quick_register() - yield client.disconnect() - yield client.send_notification(data=data, topic="Inbox") - yield client.send_notification(data=data2, topic="Inbox") - yield client.connect() - yield client.hello() - result = yield client.get_notification() - assert result["headers"]["encryption"] == client._crypto_key - assert result["data"] == base64url_encode(data2) - assert result["messageType"] == "notification" - result = yield client.get_notification() - assert result is None - yield self.shut_down(client) - - @inlineCallbacks - def test_topic_no_delivery_on_reconnect(self): - data = str(uuid.uuid4()) - client = yield self.quick_register() - yield client.disconnect() - yield client.send_notification(data=data, topic="Inbox") - yield client.connect() - yield client.hello() - result = yield client.get_notification(timeout=10) - assert result["headers"]["encryption"] == client._crypto_key - assert result["data"] == base64url_encode(data) - assert result["messageType"] == "notification" - yield client.ack(result["channelID"], result["version"]) - yield client.disconnect() - yield client.connect() - yield client.hello() - result = yield client.get_notification() - assert result is None - yield client.disconnect() - yield client.connect() - yield client.hello() - yield self.shut_down(client) - - @inlineCallbacks - def test_basic_delivery_with_vapid(self): - data = str(uuid.uuid4()) - client = yield self.quick_register() - vapid_info = _get_vapid() - result = yield client.send_notification(data=data, vapid=vapid_info) - assert result["headers"]["encryption"] == client._crypto_key - assert result["data"] == base64url_encode(data) - assert result["messageType"] == "notification" - assert self.logs.logged_ci(lambda ci: 'router_key' in ci) - yield self.shut_down(client) - - @inlineCallbacks - def test_basic_delivery_with_invalid_vapid(self): - data = str(uuid.uuid4()) - client = yield self.quick_register() - vapid_info = _get_vapid() - vapid_info['crypto-key'] = "invalid" - yield client.send_notification( - data=data, - vapid=vapid_info, - status=401) - yield self.shut_down(client) - - @inlineCallbacks - def test_basic_delivery_with_invalid_vapid_exp(self): - data = str(uuid.uuid4()) - client = yield self.quick_register() - vapid_info = _get_vapid( - payload={"aud": "https://pusher_origin.example.com", - "exp": '@', - "sub": "mailto:admin@example.com"}) - vapid_info['crypto-key'] = "invalid" - yield client.send_notification( - data=data, - vapid=vapid_info, - status=401) - yield self.shut_down(client) - - @inlineCallbacks - def test_basic_delivery_with_invalid_vapid_auth(self): - data = str(uuid.uuid4()) - client = yield self.quick_register() - vapid_info = _get_vapid() - vapid_info['auth'] = "" - yield client.send_notification( - data=data, - vapid=vapid_info, - status=401) - yield self.shut_down(client) - - @inlineCallbacks - def test_basic_delivery_with_invalid_signature(self): - data = str(uuid.uuid4()) - client = yield self.quick_register() - vapid_info = _get_vapid( - payload={"aud": "https://pusher_origin.example.com", - "sub": "mailto:admin@example.com"}) - vapid_info['auth'] = vapid_info['auth'][:-3] + "bad" - yield client.send_notification( - data=data, - vapid=vapid_info, - status=401) - yield self.shut_down(client) - - @inlineCallbacks - def test_basic_delivery_with_invalid_vapid_ckey(self): - data = str(uuid.uuid4()) - client = yield self.quick_register() - vapid_info = _get_vapid() - vapid_info['crypto-key'] = "invalid|" - yield client.send_notification( - data=data, - vapid=vapid_info, - status=401) - yield self.shut_down(client) - - @inlineCallbacks - def test_delivery_repeat_without_ack(self): - data = str(uuid.uuid4()) - client = yield self.quick_register() - yield client.disconnect() - assert client.channels - yield client.send_notification(data=data) - yield client.connect() - yield client.hello() - result = yield client.get_notification() - assert result != {} - assert result["data"] == base64url_encode(data) - - yield client.disconnect() - yield client.connect() - yield client.hello() - result = yield client.get_notification() - assert result != {} - assert result["data"] == base64url_encode(data) - yield self.shut_down(client) - - @inlineCallbacks - def test_repeat_delivery_with_disconnect_without_ack(self): - data = str(uuid.uuid4()) - client = yield self.quick_register() - result = yield client.send_notification(data=data) - assert result != {} - assert result["data"] == base64url_encode(data) - yield client.disconnect() - yield client.connect() - yield client.hello() - result = yield client.get_notification() - assert result != {} - assert result["data"] == base64url_encode(data) - yield self.shut_down(client) - - @inlineCallbacks - def test_multiple_delivery_repeat_without_ack(self): - data = str(uuid.uuid4()) - data2 = str(uuid.uuid4()) - client = yield self.quick_register() - yield client.disconnect() - assert client.channels - yield client.send_notification(data=data) - yield client.send_notification(data=data2) - yield client.connect() - yield client.hello() - result = yield client.get_notification() - assert result != {} - assert result["data"] in map(base64url_encode, [data, data2]) - result = yield client.get_notification() - assert result != {} - assert result["data"] in map(base64url_encode, [data, data2]) - - yield client.disconnect() - yield client.connect() - yield client.hello() - result = yield client.get_notification() - assert result != {} - assert result["data"] in map(base64url_encode, [data, data2]) - result = yield client.get_notification() - assert result != {} - assert result["data"] in map(base64url_encode, [data, data2]) - yield self.shut_down(client) - - @inlineCallbacks - def test_multiple_legacy_delivery_with_single_ack(self): - data = str(uuid.uuid4()) - data2 = str(uuid.uuid4()) - client = yield self.quick_register() - yield client.disconnect() - assert client.channels - with self.legacy_endpoint(): - yield client.send_notification(data=data) - yield client.send_notification(data=data2) - yield client.connect() - yield client.hello() - result = yield client.get_notification(timeout=0.5) - assert result != {} - assert result["data"] in map(base64url_encode, [data, data2]) - result = yield client.get_notification() - assert result != {} - assert result["data"] in map(base64url_encode, [data, data2]) - yield client.ack(result["channelID"], result["version"]) - - yield client.disconnect() - yield client.connect() - yield client.hello() - result = yield client.get_notification() - assert result != {} - assert result["data"] in map(base64url_encode, [data, data2]) - assert result["messageType"] == "notification" - result = yield client.get_notification(timeout=0.5) - assert result is None - yield self.shut_down(client) - - @inlineCallbacks - def test_multiple_delivery_with_single_ack(self): - data = str(uuid.uuid4()) - data2 = str(uuid.uuid4()) - client = yield self.quick_register() - yield client.disconnect() - assert client.channels - yield client.send_notification(data=data) - yield client.send_notification(data=data2) - yield client.connect() - yield client.hello() - result = yield client.get_notification(timeout=0.5) - assert result != {} - assert result["data"] == base64url_encode(data) - result2 = yield client.get_notification(timeout=0.5) - assert result2 != {} - assert result2["data"] == base64url_encode(data2) - yield client.ack(result["channelID"], result["version"]) - - yield client.disconnect() - yield client.connect() - yield client.hello() - result = yield client.get_notification(timeout=0.5) - assert result != {} - assert result["data"] == base64url_encode(data) - assert result["messageType"] == "notification" - result2 = yield client.get_notification() - assert result2 != {} - assert result2["data"] == base64url_encode(data2) - yield client.ack(result["channelID"], result["version"]) - yield client.ack(result2["channelID"], result2["version"]) - - # Verify no messages are delivered - yield client.disconnect() - yield client.connect() - yield client.hello() - result = yield client.get_notification(timeout=0.5) - assert result is None - yield self.shut_down(client) - - @inlineCallbacks - def test_multiple_delivery_with_multiple_ack(self): - data = str(uuid.uuid4()) - data2 = str(uuid.uuid4()) - client = yield self.quick_register() - yield client.disconnect() - assert client.channels - yield client.send_notification(data=data) - yield client.send_notification(data=data2) - yield client.connect() - yield client.hello() - result = yield client.get_notification(timeout=0.5) - assert result != {} - assert result["data"] in map(base64url_encode, [data, data2]) - result2 = yield client.get_notification() - assert result2 != {} - assert result2["data"] in map(base64url_encode, [data, data2]) - yield client.ack(result2["channelID"], result2["version"]) - yield client.ack(result["channelID"], result["version"]) - - yield client.disconnect() - yield client.connect() - yield client.hello() - result = yield client.get_notification(timeout=0.5) - assert result is None - yield self.shut_down(client) - - @inlineCallbacks - def test_no_delivery_to_unregistered(self): - data = str(uuid.uuid4()) - client = yield self.quick_register() # type: Client - assert client.channels - chan = client.channels.keys()[0] - - result = yield client.send_notification(data=data) - assert result["channelID"] == chan - assert result["data"] == base64url_encode(data) - yield client.ack(result["channelID"], result["version"]) - - yield client.unregister(chan) - result = yield client.send_notification(data=data, status=410) - - # Verify cache-control - assert client.notif_response.getheader("Cache-Control") == \ - "max-age=86400" - - assert result is None - yield self.shut_down(client) - - @inlineCallbacks - def test_ttl_0_connected(self): - data = str(uuid.uuid4()) - client = yield self.quick_register() - result = yield client.send_notification(data=data, ttl=0) - assert result is not None - assert result["headers"]["encryption"] == client._crypto_key - assert result["data"] == base64url_encode(data) - assert result["messageType"] == "notification" - yield self.shut_down(client) - - @inlineCallbacks - def test_ttl_0_not_connected(self): - data = str(uuid.uuid4()) - client = yield self.quick_register() - yield client.disconnect() - yield client.send_notification(data=data, ttl=0) - yield client.connect() - yield client.hello() - result = yield client.get_notification(timeout=0.5) - assert result is None - yield self.shut_down(client) - - @inlineCallbacks - def test_ttl_expired(self): - data = str(uuid.uuid4()) - client = yield self.quick_register() - yield client.disconnect() - yield client.send_notification(data=data, ttl=1) - time.sleep(1) - yield client.connect() - yield client.hello() - result = yield client.get_notification(timeout=0.5) - assert result is None - yield self.shut_down(client) - - @inlineCallbacks - def test_ttl_batch_expired_and_good_one(self): - data = str(uuid.uuid4()) - data2 = str(uuid.uuid4()) - client = yield self.quick_register() - yield client.disconnect() - for x in range(0, 12): - yield client.send_notification(data=data, ttl=1) - - yield client.send_notification(data=data2) - time.sleep(1) - yield client.connect() - yield client.hello() - result = yield client.get_notification(timeout=4) - assert result is not None - assert result["headers"]["encryption"] == client._crypto_key - assert result["data"] == base64url_encode(data2) - assert result["messageType"] == "notification" - result = yield client.get_notification(timeout=0.5) - assert result is None - yield self.shut_down(client) - - @inlineCallbacks - def test_ttl_batch_partly_expired_and_good_one(self): - data = str(uuid.uuid4()) - data1 = str(uuid.uuid4()) - data2 = str(uuid.uuid4()) - client = yield self.quick_register() - yield client.disconnect() - for x in range(0, 6): - yield client.send_notification(data=data) - - for x in range(0, 6): - yield client.send_notification(data=data1, ttl=1) - - yield client.send_notification(data=data2) - time.sleep(1) - yield client.connect() - yield client.hello() - - # Pull out and ack the first - for x in range(0, 6): - result = yield client.get_notification(timeout=4) - assert result is not None - assert result["data"] == base64url_encode(data) - yield client.ack(result["channelID"], result["version"]) - - # Should have one more that is data2, this will only arrive if the - # other six were acked as that hits the batch size - result = yield client.get_notification(timeout=4) - assert result is not None - assert result["data"] == base64url_encode(data2) - - # No more - result = yield client.get_notification() - assert result is None - yield self.shut_down(client) - - @inlineCallbacks - def test_message_without_crypto_headers(self): - data = str(uuid.uuid4()) - client = yield self.quick_register() - result = yield client.send_notification(data=data, use_header=False, - status=400) - assert result is None - yield self.shut_down(client) - - @inlineCallbacks - def test_empty_message_without_crypto_headers(self): - client = yield self.quick_register() - result = yield client.send_notification(use_header=False) - assert result is not None - assert result["messageType"] == "notification" - assert "headers" not in result - assert "data" not in result - yield client.ack(result["channelID"], result["version"]) - - yield client.disconnect() - yield client.send_notification(use_header=False) - yield client.connect() - yield client.hello() - result = yield client.get_notification() - assert result is not None - assert "headers" not in result - assert "data" not in result - yield client.ack(result["channelID"], result["version"]) - - yield self.shut_down(client) - - @inlineCallbacks - def test_empty_message_with_crypto_headers(self): - client = yield self.quick_register() - result = yield client.send_notification() - assert result is not None - assert result["messageType"] == "notification" - assert "headers" not in result - assert "data" not in result - - result2 = yield client.send_notification() - # We shouldn't store headers for blank messages. - assert result2 is not None - assert result2["messageType"] == "notification" - assert "headers" not in result2 - assert "data" not in result2 - - yield client.ack(result["channelID"], result["version"]) - yield client.ack(result2["channelID"], result2["version"]) - - yield client.disconnect() - yield client.send_notification() - yield client.connect() - yield client.hello() - result3 = yield client.get_notification() - assert result3 is not None - assert "headers" not in result3 - assert "data" not in result3 - yield client.ack(result3["channelID"], result3["version"]) - - yield self.shut_down(client) - - @inlineCallbacks - def test_delete_saved_notification(self): - client = yield self.quick_register() - yield client.disconnect() - assert client.channels - chan = client.channels.keys()[0] - yield client.send_notification() - yield client.delete_notification(chan) - yield client.connect() - yield client.hello() - result = yield client.get_notification() - assert result is None - yield self.shut_down(client) - - @inlineCallbacks - def test_with_key(self): - private_key = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p) - claims = {"aud": "http://example.com", - "exp": int(time.time()) + 86400, - "sub": "a@example.com"} - vapid = _get_vapid(private_key, claims) - pk_hex = vapid['crypto-key'] - chid = str(uuid.uuid4()) - client = Client("ws://localhost:{}/".format(CONNECTION_PORT)) - yield client.connect() - yield client.hello() - yield client.register(chid=chid, key=pk_hex) - - # Send an update with a properly formatted key. - yield client.send_notification(vapid=vapid) - - # now try an invalid key. - new_key = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p) - vapid = _get_vapid(new_key, claims) - - yield client.send_notification( - vapid=vapid, - status=401) - - yield self.shut_down(client) - - @inlineCallbacks - def test_with_bad_key(self): - chid = str(uuid.uuid4()) - client = Client("ws://localhost:{}/".format(CONNECTION_PORT)) - yield client.connect() - yield client.hello() - result = yield client.register(chid=chid, key="af1883%&!@#*(", - status=400) - assert result["status"] == 400 - - yield self.shut_down(client) - - -class TestRustWebPushBroadcast(unittest.TestCase): - _endpoint_defaults = dict( - hostname='localhost', - port=ENDPOINT_PORT, - endpoint_port=ENDPOINT_PORT, - endpoint_scheme='http', - router_port=MP_ROUTER_PORT, - statsd_host=None, - router_table=dict(tablename=ROUTER_TABLE), - message_table=dict(tablename=MESSAGE_TABLE), - use_cryptography=True, - ) - - _conn_defaults = dict( - hostname='localhost', - port=RP_CONNECTION_PORT, - endpoint_port=ENDPOINT_PORT, - router_port=RP_ROUTER_PORT, - endpoint_scheme='http', - statsd_host=None, - router_table=dict(tablename=ROUTER_TABLE), - message_table=dict(tablename=MESSAGE_TABLE), - use_cryptography=True, - human_logs=False, - ) - - def start_ep(self, ep_conf): - # Endpoint HTTP router - self.ep = ep = EndpointApplication( - ep_conf, - resource=autopush.tests.boto_resource - ) - ep.setup(rotate_tables=False) - ep.startService() - self.addCleanup(ep.stopService) - - def setUp(self): - self.mock_server_thread = MOCK_SERVER_THREAD - self.mock_megaphone = MockMegaphoneRequestHandler - - self.logs = TestingLogObserver() - begin_or_register(self.logs) - self.addCleanup(globalLogPublisher.removeObserver, self.logs) - - self._ep_conf = AutopushConfig( - crypto_key=CRYPTO_KEY, - **self.endpoint_kwargs() - ) - - self.start_ep(self._ep_conf) - - # Create a Python connection application for accessing the db - self._conn_conf = AutopushConfig( - crypto_key=CRYPTO_KEY, - **self.conn_kwargs() - ) - self.conn = conn = ConnectionApplication( - self._conn_conf, - resource=autopush.tests.boto_resource, - ) - conn.setup(rotate_tables=True) - - def tearDown(self): - for queue in CN_QUEUES: - is_empty = False - while not is_empty: - try: - line = queue.get_nowait() - except Empty: - is_empty = True - else: - print(line) - - def endpoint_kwargs(self): - return self._endpoint_defaults - - def conn_kwargs(self): - return self._conn_defaults - - @inlineCallbacks - def quick_register(self, sslcontext=None, connection_port=None): - conn_port = connection_port or MP_CONNECTION_PORT - client = Client("ws://localhost:{}/".format(conn_port), - sslcontext=sslcontext) - yield client.connect() - yield client.hello() - yield client.register() - returnValue(client) - - @inlineCallbacks - def shut_down(self, client=None): - if client: - yield client.disconnect() - - @contextmanager - def legacy_endpoint(self): - self.ep.conf._notification_legacy = True - yield - self.ep.conf._notification_legacy = False - - @property - def _ws_url(self): - return "ws://localhost:{}/".format(MP_CONNECTION_PORT) - - @inlineCallbacks - def test_broadcast_update_on_connect(self): - self.mock_megaphone.services = {"kinto:123": "ver1"} - self.mock_megaphone.polled.clear() - self.mock_megaphone.polled.wait(timeout=5) - - old_ver = {"kinto:123": "ver0"} - client = Client(self._ws_url) - yield client.connect() - result = yield client.hello(services=old_ver) - assert result != {} - assert result["use_webpush"] is True - assert result["broadcasts"]["kinto:123"] == "ver1" - - self.mock_megaphone.services = {"kinto:123": "ver2"} - self.mock_megaphone.polled.clear() - self.mock_megaphone.polled.wait(timeout=5) - - result = yield client.get_broadcast(2) - assert result["broadcasts"]["kinto:123"] == "ver2" - - yield self.shut_down(client) - - @inlineCallbacks - def test_broadcast_subscribe(self): - self.mock_megaphone.services = {"kinto:123": "ver1"} - self.mock_megaphone.polled.clear() - self.mock_megaphone.polled.wait(timeout=5) - - old_ver = {"kinto:123": "ver0"} - client = Client(self._ws_url) - yield client.connect() - result = yield client.hello() - assert result != {} - assert result["use_webpush"] is True - assert result["broadcasts"] == {} - - client.broadcast_subscribe(old_ver) - result = yield client.get_broadcast() - assert result["broadcasts"]["kinto:123"] == "ver1" - - self.mock_megaphone.services = {"kinto:123": "ver2"} - self.mock_megaphone.polled.clear() - self.mock_megaphone.polled.wait(timeout=5) - - result = yield client.get_broadcast(2) - assert result["broadcasts"]["kinto:123"] == "ver2" - - yield self.shut_down(client) - - @inlineCallbacks - def test_broadcast_no_changes(self): - self.mock_megaphone.services = {"kinto:123": "ver1"} - self.mock_megaphone.polled.clear() - self.mock_megaphone.polled.wait(timeout=5) - - old_ver = {"kinto:123": "ver1"} - client = Client(self._ws_url) - yield client.connect() - result = yield client.hello(services=old_ver) - assert result != {} - assert result["use_webpush"] is True - assert result["broadcasts"] == {} - - yield self.shut_down(client) - - @inlineCallbacks - def test_webpush_monthly_rotation(self): - from autopush.db import make_rotating_tablename - client = yield self.quick_register() - yield client.disconnect() - - # Move the client back one month to the past - last_month = make_rotating_tablename( - prefix=self.conn.conf.message_table.tablename, delta=-1) - lm_message = Message(last_month, boto_resource=self.conn.db.resource) - yield deferToThread( - self.conn.db.router.update_message_month, - client.uaid, - last_month, - ) - - # Verify the move - c = yield deferToThread(self.conn.db.router.get_uaid, - client.uaid) - assert c["current_month"] == last_month - - # Verify last_connect is current, then move that back - assert has_connected_this_month(c) - today = get_month(delta=-1) - last_connect = int("%s%s020001" % (today.year, - str(today.month).zfill(2))) - - yield deferToThread( - self.conn.db.router._update_last_connect, - client.uaid, - last_connect) - c = yield deferToThread(self.conn.db.router.get_uaid, - client.uaid) - assert has_connected_this_month(c) is False - - # Move the clients channels back one month - exists, chans = yield deferToThread( - self.conn.db.message.all_channels, - client.uaid - ) - assert exists is True - assert len(chans) == 1 - yield deferToThread( - lm_message.save_channels, - client.uaid, - chans, - ) - - # Remove the channels entry entirely from this month - yield deferToThread( - self.conn.db.message.table.delete_item, - Key={'uaid': client.uaid, 'chidmessageid': ' '} - ) - - # Verify the channel is gone - exists, chans = yield deferToThread( - self.conn.db.message.all_channels, - client.uaid, - ) - assert exists is False - assert len(chans) == 0 - - # Send in a notification, verify it landed in last months notification - # table - data = uuid.uuid4().hex - with self.legacy_endpoint(): - yield client.send_notification(data=data) - ts, notifs = yield deferToThread(lm_message.fetch_timestamp_messages, - uuid.UUID(client.uaid), - " ") - assert len(notifs) == 1 - - # Connect the client, verify the migration - yield client.connect() - yield client.hello() - - # Pull down the notification - result = yield client.get_notification() - chan = client.channels.keys()[0] - assert result is not None - assert chan == result["channelID"] - - # Acknowledge the notification, which triggers the migration - yield client.ack(chan, result["version"]) - - # Wait up to 4 seconds for the table rotation to occur - start = time.time() - while time.time()-start < 4: - c = yield deferToThread( - self.conn.db.router.get_uaid, - client.uaid) - if c["current_month"] == self.conn.db.current_msg_month: - break - else: - yield deferToThread(time.sleep, 0.2) - - # Verify the month update in the router table - c = yield deferToThread( - self.conn.db.router.get_uaid, - client.uaid) - assert c["current_month"] == self.conn.db.current_msg_month - - # Verify the client moved last_connect - assert has_connected_this_month(c) is True - - # Verify the channels were moved - exists, chans = yield deferToThread( - self.conn.db.message.all_channels, - client.uaid - ) - assert exists is True - assert len(chans) == 1 - yield self.shut_down(client) - - @inlineCallbacks - def test_webpush_monthly_rotation_prior_record_exists(self): - from autopush.db import make_rotating_tablename - client = yield self.quick_register() - yield client.disconnect() - - # Move the client back one month to the past - last_month = make_rotating_tablename( - prefix=self.conn.conf.message_table.tablename, delta=-1) - lm_message = Message(last_month, - boto_resource=autopush.tests.boto_resource) - yield deferToThread( - self.conn.db.router.update_message_month, - client.uaid, - last_month, - ) - - # Verify the move - c = yield deferToThread(self.conn.db.router.get_uaid, - client.uaid) - assert c["current_month"] == last_month - - # Verify last_connect is current, then move that back - assert has_connected_this_month(c) - today = get_month(delta=-1) - yield deferToThread( - self.conn.db.router._update_last_connect, - client.uaid, - int("%s%s020001" % (today.year, str(today.month).zfill(2))), - ) - c = yield deferToThread(self.conn.db.router.get_uaid, client.uaid) - assert has_connected_this_month(c) is False - - # Move the clients channels back one month - exists, chans = yield deferToThread( - self.conn.db.message.all_channels, - client.uaid, - ) - assert exists is True - assert len(chans) == 1 - yield deferToThread( - lm_message.save_channels, - client.uaid, - chans, - ) - - # Send in a notification, verify it landed in last months notification - # table - data = uuid.uuid4().hex - with self.legacy_endpoint(): - yield client.send_notification(data=data) - _, notifs = yield deferToThread(lm_message.fetch_timestamp_messages, - uuid.UUID(client.uaid), - " ") - assert len(notifs) == 1 - - # Connect the client, verify the migration - yield client.connect() - yield client.hello() - - # Pull down the notification - result = yield client.get_notification() - chan = client.channels.keys()[0] - assert result is not None - assert chan == result["channelID"] - - # Acknowledge the notification, which triggers the migration - yield client.ack(chan, result["version"]) - - # Wait up to 4 seconds for the table rotation to occur - start = time.time() - while time.time()-start < 4: - c = yield deferToThread( - self.conn.db.router.get_uaid, - client.uaid) - if c["current_month"] == self.conn.db.current_msg_month: - break - else: - yield deferToThread(time.sleep, 0.2) - - # Verify the month update in the router table - c = yield deferToThread(self.conn.db.router.get_uaid, client.uaid) - assert c["current_month"] == self.conn.db.current_msg_month - - # Verify the client moved last_connect - assert has_connected_this_month(c) is True - - # Verify the channels were moved - exists, chans = yield deferToThread( - self.conn.db.message.all_channels, - client.uaid - ) - assert exists is True - assert len(chans) == 1 - yield self.shut_down(client) - - @inlineCallbacks - def test_webpush_monthly_rotation_no_channels(self): - from autopush.db import make_rotating_tablename - client = Client("ws://localhost:{}/".format(MP_CONNECTION_PORT)) - yield client.connect() - yield client.hello() - yield client.disconnect() - - # Move the client back one month to the past - last_month = make_rotating_tablename( - prefix=self.conn.conf.message_table.tablename, delta=-1) - yield deferToThread( - self.conn.db.router.update_message_month, - client.uaid, - last_month - ) - - # Verify the move - c = yield deferToThread(self.conn.db.router.get_uaid, - client.uaid - ) - assert c["current_month"] == last_month - - # Verify there's no channels - exists, chans = yield deferToThread( - self.conn.db.message.all_channels, - client.uaid, - ) - assert exists is False - assert len(chans) == 0 - - # Connect the client, verify the migration - yield client.connect() - yield client.hello() - - # Wait up to 2 seconds for the table rotation to occur - start = time.time() - while time.time()-start < 2: - c = yield deferToThread( - self.conn.db.router.get_uaid, - client.uaid, - ) - if c["current_month"] == self.conn.db.current_msg_month: - break - else: - yield deferToThread(time.sleep, 0.2) - - # Verify the month update in the router table - c = yield deferToThread(self.conn.db.router.get_uaid, - client.uaid) - assert c["current_month"] == self.conn.db.current_msg_month - yield self.shut_down(client) - - -class TestRustAndPythonWebPush(unittest.TestCase): - _endpoint_defaults = dict( - hostname='localhost', - port=ENDPOINT_PORT, - endpoint_port=ENDPOINT_PORT, - endpoint_scheme='http', - router_port=RP_ROUTER_PORT, - statsd_host=None, - router_table=dict(tablename=ROUTER_TABLE), - message_table=dict(tablename=MESSAGE_TABLE), - use_cryptography=True, - ) - - _conn_defaults = dict( - hostname='localhost', - port=RP_CONNECTION_PORT, - endpoint_port=ENDPOINT_PORT, - router_port=RP_ROUTER_PORT, - endpoint_scheme='http', - statsd_host=None, - router_table=dict(tablename=ROUTER_TABLE), - message_table=dict(tablename=MESSAGE_TABLE), - use_cryptography=True, - human_logs=False, - ) - - def start_ep(self, ep_conf): - # Endpoint HTTP router - self.ep = ep = EndpointApplication( - ep_conf, - resource=autopush.tests.boto_resource - ) - ep.setup(rotate_tables=False) - ep.startService() - self.addCleanup(ep.stopService) - - def start_conn(self, conn_conf): - # Startup only the Python connection application as we will use - # the module global Rust one as well - self.conn = conn = ConnectionApplication( - conn_conf, - resource=autopush.tests.boto_resource, - ) - conn.setup(rotate_tables=False) - conn.startService() - self.addCleanup(conn.stopService) - - def setUp(self): - self.logs = TestingLogObserver() - begin_or_register(self.logs) - self.addCleanup(globalLogPublisher.removeObserver, self.logs) - - self._ep_conf = AutopushConfig( - crypto_key=CRYPTO_KEY, - **self.endpoint_kwargs() - ) - self._conn_conf = AutopushConfig( - crypto_key=CRYPTO_KEY, - **self.conn_kwargs() - ) - - self.start_ep(self._ep_conf) - self.start_conn(self._conn_conf) - - def tearDown(self): - for queue in CN_QUEUES: - is_empty = False - while not is_empty: - try: - line = queue.get_nowait() - except Empty: - is_empty = True - else: - print(line) - - def endpoint_kwargs(self): - return self._endpoint_defaults - - def conn_kwargs(self): - return self._conn_defaults - - @inlineCallbacks - def quick_register(self, sslcontext=None, connection_port=None): - conn_port = connection_port or RP_CONNECTION_PORT - client = Client("ws://localhost:{}/".format(conn_port), - sslcontext=sslcontext) - yield client.connect() - yield client.hello() - yield client.register() - returnValue(client) - - @inlineCallbacks - def shut_down(self, client=None): - if client: - yield client.disconnect() - - @inlineCallbacks - def test_cross_topic_no_delivery_on_reconnect(self): - data = str(uuid.uuid4()) - client = yield self.quick_register(connection_port=CONNECTION_PORT) - yield client.disconnect() - yield client.send_notification(data=data, topic="Inbox") - yield client.connect(connection_port=RP_CONNECTION_PORT) - yield client.hello() - result = yield client.get_notification(timeout=10) - assert result["headers"]["encryption"] == client._crypto_key - assert result["data"] == base64url_encode(data) - assert result["messageType"] == "notification" - yield client.ack(result["channelID"], result["version"]) - yield client.disconnect() - yield client.connect(connection_port=CONNECTION_PORT) - yield client.hello() - result = yield client.get_notification(0.5) - assert result is None - yield client.disconnect() - yield client.connect(connection_port=RP_CONNECTION_PORT) - yield client.hello() - result = yield client.get_notification(0.5) - assert result is None - yield self.shut_down(client) - - @inlineCallbacks - def test_cross_topic_no_delivery_on_reconnect_reverse(self): - data = str(uuid.uuid4()) - client = yield self.quick_register() - yield client.disconnect() - yield client.send_notification(data=data, topic="Inbox") - yield client.connect(connection_port=CONNECTION_PORT) - yield client.hello() - result = yield client.get_notification(timeout=10) - assert result["headers"]["encryption"] == client._crypto_key - assert result["data"] == base64url_encode(data) - assert result["messageType"] == "notification" - yield client.ack(result["channelID"], result["version"]) - yield client.disconnect() - yield client.connect(connection_port=RP_CONNECTION_PORT) - yield client.hello() - result = yield client.get_notification(0.5) - assert result is None - yield client.disconnect() - yield client.connect(connection_port=CONNECTION_PORT) - yield client.hello() - result = yield client.get_notification(0.5) - assert result is None - yield self.shut_down(client) - - @inlineCallbacks - def test_cross_multiple_delivery_with_single_ack(self): - data = str(uuid.uuid4()) - data2 = str(uuid.uuid4()) - client = yield self.quick_register(connection_port=CONNECTION_PORT) - yield client.disconnect() - assert client.channels - yield client.send_notification(data=data) - yield client.send_notification(data=data2) - yield client.connect() - yield client.hello() - result = yield client.get_notification(timeout=0.5) - assert result != {} - assert result["data"] == base64url_encode(data) - result2 = yield client.get_notification(timeout=0.5) - assert result2 != {} - assert result2["data"] == base64url_encode(data2) - yield client.ack(result["channelID"], result["version"]) - - yield client.disconnect() - yield client.connect(connection_port=CONNECTION_PORT) - yield client.hello() - result = yield client.get_notification(timeout=0.5) - assert result != {} - assert result["data"] == base64url_encode(data) - assert result["messageType"] == "notification" - result2 = yield client.get_notification() - assert result2 != {} - assert result2["data"] == base64url_encode(data2) - yield client.ack(result["channelID"], result["version"]) - yield client.ack(result2["channelID"], result2["version"]) - - # Verify no messages are delivered - yield client.disconnect() - yield client.connect() - yield client.hello() - result = yield client.get_notification(timeout=0.5) - assert result is None - yield self.shut_down(client) - - @inlineCallbacks - def test_cross_multiple_delivery_with_single_ack_reverse(self): - data = str(uuid.uuid4()) - data2 = str(uuid.uuid4()) - client = yield self.quick_register() - yield client.disconnect() - assert client.channels - yield client.send_notification(data=data) - yield client.send_notification(data=data2) - yield client.connect(connection_port=CONNECTION_PORT) - yield client.hello() - result = yield client.get_notification(timeout=0.5) - assert result != {} - assert result["data"] == base64url_encode(data) - result2 = yield client.get_notification(timeout=0.5) - assert result2 != {} - assert result2["data"] == base64url_encode(data2) - yield client.ack(result["channelID"], result["version"]) - - yield client.disconnect() - yield client.connect() - yield client.hello() - result = yield client.get_notification(timeout=0.5) - assert result != {} - assert result["data"] == base64url_encode(data) - assert result["messageType"] == "notification" - result2 = yield client.get_notification() - assert result2 != {} - assert result2["data"] == base64url_encode(data2) - yield client.ack(result["channelID"], result["version"]) - yield client.ack(result2["channelID"], result2["version"]) - - # Verify no messages are delivered - yield client.disconnect() - yield client.connect(connection_port=CONNECTION_PORT) - yield client.hello() - result = yield client.get_notification(timeout=0.5) - assert result is None - yield self.shut_down(client) - - @inlineCallbacks - def test_cross_multiple_delivery_with_multiple_ack(self): - data = str(uuid.uuid4()) - data2 = str(uuid.uuid4()) - client = yield self.quick_register() - yield client.disconnect() - assert client.channels - yield client.send_notification(data=data) - yield client.send_notification(data=data2) - yield client.connect(connection_port=CONNECTION_PORT) - yield client.hello() - result = yield client.get_notification(timeout=0.5) - assert result != {} - assert result["data"] in map(base64url_encode, [data, data2]) - result2 = yield client.get_notification() - assert result2 != {} - assert result2["data"] in map(base64url_encode, [data, data2]) - yield client.ack(result2["channelID"], result2["version"]) - yield client.ack(result["channelID"], result["version"]) - - yield client.disconnect() - yield client.connect() - yield client.hello() - result = yield client.get_notification(timeout=0.5) - assert result is None - yield self.shut_down(client) - - @inlineCallbacks - def test_cross_multiple_delivery_with_multiple_ack_reverse(self): - data = str(uuid.uuid4()) - data2 = str(uuid.uuid4()) - client = yield self.quick_register(connection_port=CONNECTION_PORT) - yield client.disconnect() - assert client.channels - yield client.send_notification(data=data) - yield client.send_notification(data=data2) - yield client.connect() - yield client.hello() - result = yield client.get_notification(timeout=0.5) - assert result != {} - assert result["data"] in map(base64url_encode, [data, data2]) - result2 = yield client.get_notification() - assert result2 != {} - assert result2["data"] in map(base64url_encode, [data, data2]) - yield client.ack(result2["channelID"], result2["version"]) - yield client.ack(result["channelID"], result["version"]) - - yield client.disconnect() - yield client.connect(connection_port=CONNECTION_PORT) - yield client.hello() - result = yield client.get_notification(timeout=0.5) - assert result is None - yield self.shut_down(client) diff --git a/autopush_rs/Cargo.toml b/autopush_rs/Cargo.toml deleted file mode 100644 index a5464363..00000000 --- a/autopush_rs/Cargo.toml +++ /dev/null @@ -1,70 +0,0 @@ -[package] -name = "autopush" -version = "0.1.0" -authors = [ - "Ben Bangert ", - "JR Conlin ", - "Alex Crichton ", - "Phil Jenvey ", -] - -[lib] -name = "autopush" - -[[bin]] -name = "autopush_rs" -path = "src/main.rs" - -[dependencies] -base64 = "0.9.1" -bytes = "0.4.8" -cadence = "0.14.0" -chan-signal = "0.3.1" -chrono = "0.4.2" -docopt = "1.0.0" -env_logger = { version = "0.5.10", default-features = false } -error-chain = "0.11.0" -fernet = "0.1.0" -futures = "0.1.21" -futures-backoff = "0.1.0" -hex = "0.3.2" -httparse = "1.2.4" -hyper = "0.11.27" -lazy_static = "1.0.1" -libc = "0.2.41" -log = { version = "0.4.1", features = ["max_level_info", "release_max_level_info"] } -matches = "0.1.6" -mozsvc-common = "0.1.0" -openssl = "0.10.8" -rand = "0.5.0" -regex = "1.0.0" -reqwest = { version = "0.8.5", features = ["unstable"] } -rusoto_core = "0.32.0" -rusoto_credential = "0.11.0" -rusoto_dynamodb = "0.32.0" -sentry = "0.2.0" -serde = "1.0.63" -serde_derive = "1.0.63" -serde_dynamodb = "0.1.2" -serde_json = "1.0.18" -slog = { version = "2.2.3" , features = ["max_level_trace", "release_max_level_info"] } -slog-async = "2.3.0" -slog-term = "2.4.0" -slog-mozlog-json = "0.1" -slog-scope = "4.0.1" -slog-stdlog = "3.0.2" -# state_machine_future = { version = "0.1.6", features = ["debug_code_generation"] } -state_machine_future = "0.1.6" -time = "0.1.40" -tokio-core = "0.1.17" -tokio-io = "0.1.6" -tokio-openssl = "0.2.0" -tokio-service = "0.1.0" -tokio-tungstenite = { version = "0.5.1", default-features = false } -tungstenite = { version = "0.5.3", default-features = false } -uuid = { version = "0.6.5", features = ["serde", "v4"] } -woothee = "0.7.3" - -[dependencies.config] -git = "https://github.com/mehcode/config-rs" -rev = "e8fa9fee96185ddd18ebcef8a925c75459111edb" diff --git a/autopush_rs/src/client.rs b/autopush_rs/src/client.rs deleted file mode 100644 index 7b560075..00000000 --- a/autopush_rs/src/client.rs +++ /dev/null @@ -1,1054 +0,0 @@ -//! Management of connected clients to a WebPush server -//! -//! This module is a pretty heavy work in progress. The intention is that -//! this'll house all the various state machine transitions and state management -//! of connected clients. Note that it's expected there'll be a lot of connected -//! clients, so this may appears relatively heavily optimized! -use std::cell::RefCell; -use std::mem; -use std::rc::Rc; - -use cadence::prelude::*; -use futures::AsyncSink; -use futures::future::Either; -use futures::sync::mpsc; -use futures::sync::oneshot::Receiver; -use futures::{Async, Future, Poll, Sink, Stream}; -use rusoto_dynamodb::UpdateItemOutput; -use state_machine_future::RentToOwn; -use tokio_core::reactor::Timeout; -use uuid::Uuid; -use woothee::parser::Parser; - -use errors::*; -use protocol::{ClientMessage, Notification, ServerMessage, ServerNotification}; -use server::Server; -use db::{CheckStorageResponse, HelloResponse, RegisterResponse}; -use util::megaphone::{ClientServices, Service, ServiceClientInit}; -use util::{ms_since_epoch, parse_user_agent, sec_since_epoch}; - -// Created and handed to the AutopushServer -pub struct RegisteredClient { - pub uaid: Uuid, - pub uid: Uuid, - pub tx: mpsc::UnboundedSender, -} - -pub struct Client - where - T: Stream - + Sink - + 'static, -{ - state_machine: UnAuthClientStateFuture, - srv: Rc, - broadcast_services: Rc>, - tx: mpsc::UnboundedSender, -} - -impl Client -where - T: Stream - + Sink - + 'static, -{ - /// Spins up a new client communicating over the websocket `ws` specified. - /// - /// The `ws` specified already has ping/pong parts of the websocket - /// protocol managed elsewhere, and this struct is only expected to deal - /// with webpush-specific messages. - /// - /// The `srv` argument is the server that this client is attached to and - /// the various state behind the server. This provides transitive access to - /// various configuration options of the server as well as the ability to - /// call back into Python. - pub fn new(ws: T, srv: &Rc, mut uarx: Receiver, host: String) -> Client { - let srv = srv.clone(); - let timeout = Timeout::new(srv.opts.open_handshake_timeout.unwrap(), &srv.handle).unwrap(); - let (tx, rx) = mpsc::unbounded(); - - // Pull out the user-agent, which we should have by now - let uastr = match uarx.poll() { - Ok(Async::Ready(ua)) => ua, - Ok(Async::NotReady) => { - error!("Failed to parse the user-agent"); - String::from("") - } - Err(_) => { - error!("Failed to receive a value"); - String::from("") - } - }; - - let broadcast_services = Rc::new(RefCell::new(Default::default())); - let sm = UnAuthClientState::start( - UnAuthClientData { - srv: srv.clone(), - ws, - user_agent: uastr, - host, - broadcast_services: broadcast_services.clone(), - }, - timeout, - tx.clone(), - rx, - ); - - Self { - state_machine: sm, - srv: srv.clone(), - broadcast_services, - tx, - } - } - - pub fn broadcast_delta(&mut self) -> Option> { - let mut broadcast_services = self.broadcast_services.borrow_mut(); - self.srv.broadcast_delta(&mut broadcast_services) - } - - pub fn shutdown(&mut self) { - let _result = self.tx.unbounded_send(ServerNotification::Disconnect); - } -} - -impl Future for Client -where - T: Stream - + Sink - + 'static, -{ - type Item = (); - type Error = Error; - - fn poll(&mut self) -> Poll<(), Error> { - self.state_machine.poll() - } -} - -// Websocket session statistics -#[derive(Clone, Default)] -struct SessionStatistics { - // User data - uaid: String, - uaid_reset: bool, - existing_uaid: bool, - connection_type: String, - host: String, - - // Usage data - direct_acked: i32, - direct_storage: i32, - stored_retrieved: i32, - stored_acked: i32, - nacks: i32, - unregisters: i32, - registers: i32, -} - -// Represent the state for a valid WebPush client that is authenticated -pub struct WebPushClient { - uaid: Uuid, - uid: Uuid, - rx: mpsc::UnboundedReceiver, - flags: ClientFlags, - message_month: String, - unacked_direct_notifs: Vec, - unacked_stored_notifs: Vec, - // Highest version from stored, retained for use with increment - // when all the unacked storeds are ack'd - unacked_stored_highest: Option, - connected_at: u64, - stats: SessionStatistics, -} - -impl Default for WebPushClient { - fn default() -> Self { - let (_, rx) = mpsc::unbounded(); - Self { - uaid: Default::default(), - uid: Default::default(), - rx, - flags: Default::default(), - message_month: Default::default(), - unacked_direct_notifs: Default::default(), - unacked_stored_notifs: Default::default(), - unacked_stored_highest: Default::default(), - connected_at: Default::default(), - stats: Default::default(), - } - } -} - -impl WebPushClient { - fn unacked_messages(&self) -> bool { - !self.unacked_stored_notifs.is_empty() || !self.unacked_direct_notifs.is_empty() - } -} - -#[derive(Default)] -pub struct ClientFlags { - include_topic: bool, - increment_storage: bool, - check: bool, - reset_uaid: bool, - rotate_message_table: bool, -} - -impl ClientFlags { - fn new() -> Self { - Self { - include_topic: true, - increment_storage: false, - check: false, - reset_uaid: false, - rotate_message_table: false, - } - } -} - -pub struct UnAuthClientData { - srv: Rc, - ws: T, - user_agent: String, - host: String, - broadcast_services: Rc>, -} - -impl UnAuthClientData -where - T: Stream - + Sink - + 'static, -{ - fn input_with_timeout(&mut self, timeout: &mut Timeout) -> Poll { - let item = match timeout.poll()? { - Async::Ready(_) => return Err("Client timed out".into()), - Async::NotReady => match self.ws.poll()? { - Async::Ready(None) => return Err("Client dropped".into()), - Async::Ready(Some(msg)) => Async::Ready(msg), - Async::NotReady => Async::NotReady, - }, - }; - Ok(item) - } -} - -pub struct AuthClientData { - srv: Rc, - ws: T, - webpush: Rc>, - broadcast_services: Rc>, -} - -impl AuthClientData -where - T: Stream - + Sink - + 'static, -{ - fn input_or_notif(&mut self) -> Poll, Error> { - let mut webpush = self.webpush.borrow_mut(); - let item = match webpush.rx.poll() { - Ok(Async::Ready(Some(notif))) => Either::B(notif), - Ok(Async::Ready(None)) => return Err("Sending side dropped".into()), - Ok(Async::NotReady) => match self.ws.poll()? { - Async::Ready(None) => return Err("Client dropped".into()), - Async::Ready(Some(msg)) => Either::A(msg), - Async::NotReady => return Ok(Async::NotReady), - }, - Err(_) => return Err("Unexpected error".into()), - }; - Ok(Async::Ready(item)) - } -} - -#[derive(StateMachineFuture)] -pub enum UnAuthClientState -where - T: Stream - + Sink - + 'static, -{ - #[state_machine_future(start, transitions(AwaitProcessHello))] - AwaitHello { - data: UnAuthClientData, - timeout: Timeout, - tx: mpsc::UnboundedSender, - rx: mpsc::UnboundedReceiver, - }, - - #[state_machine_future(transitions(AwaitSessionComplete))] - AwaitProcessHello { - response: MyFuture, - data: UnAuthClientData, - interested_broadcasts: Vec, - tx: mpsc::UnboundedSender, - rx: mpsc::UnboundedReceiver, - }, - - #[state_machine_future(transitions(UnAuthDone))] - AwaitSessionComplete { - auth_state_machine: AuthClientStateFuture, - srv: Rc, - user_agent: String, - webpush: Rc>, - }, - - #[state_machine_future(ready)] - UnAuthDone(()), - - #[state_machine_future(error)] - UnAuthClientStateError(Error), -} - -impl PollUnAuthClientState for UnAuthClientState -where - T: Stream - + Sink - + 'static, -{ - fn poll_await_hello<'a>( - hello: &'a mut RentToOwn<'a, AwaitHello>, - ) -> Poll, Error> { - trace!("State: AwaitHello"); - let (uaid, services) = { - let AwaitHello { - ref mut data, - ref mut timeout, - .. - } = **hello; - match try_ready!(data.input_with_timeout(timeout)) { - ClientMessage::Hello { - uaid, - use_webpush: Some(true), - broadcasts, - .. - } => ( - uaid.and_then(|uaid| Uuid::parse_str(uaid.as_str()).ok()), - Service::from_hashmap(broadcasts.unwrap_or_default()), - ), - _ => return Err("Invalid message, must be hello".into()), - } - }; - - let AwaitHello { data, tx, rx, .. } = hello.take(); - let connected_at = ms_since_epoch(); - let response = Box::new(data.srv.ddb.hello( - &connected_at, - uaid.as_ref(), - &data.srv.opts.router_table_name, - &data.srv.opts.router_url, - &data.srv.opts.message_table_names, - &data.srv.opts.current_message_month, - &data.srv.metrics, - )); - transition!(AwaitProcessHello { - response, - data, - interested_broadcasts: services, - tx, - rx, - }) - } - - fn poll_await_process_hello<'a>( - process_hello: &'a mut RentToOwn<'a, AwaitProcessHello>, - ) -> Poll, Error> { - trace!("State: AwaitProcessHello"); - let (uaid, message_month, check_storage, reset_uaid, rotate_message_table, connected_at) = { - match try_ready!(process_hello.response.poll()) { - HelloResponse { - uaid: Some(uaid), - message_month, - check_storage, - reset_uaid, - rotate_message_table, - connected_at, - } => ( - uaid, - message_month, - check_storage, - reset_uaid, - rotate_message_table, - connected_at, - ), - HelloResponse { uaid: None, .. } => { - return Err("Already connected elsewhere".into()) - } - } - }; - - let AwaitProcessHello { - data, - interested_broadcasts, - tx, - rx, - .. - } = process_hello.take(); - data.srv.metrics.incr("ua.command.hello").ok(); - - let UnAuthClientData { - srv, - ws, - user_agent, - host, - broadcast_services, - } = data; - - // Setup the objects and such needed for a WebPushClient - let mut flags = ClientFlags::new(); - flags.check = check_storage; - flags.reset_uaid = reset_uaid; - flags.rotate_message_table = rotate_message_table; - let ServiceClientInit(client_services, broadcasts) = - srv.broadcast_init(&interested_broadcasts); - broadcast_services.replace(client_services); - let uid = Uuid::new_v4(); - let webpush = Rc::new(RefCell::new(WebPushClient { - uaid, - uid: uid, - flags, - rx, - message_month, - connected_at, - stats: SessionStatistics { - uaid: uaid.simple().to_string(), - uaid_reset: reset_uaid, - existing_uaid: check_storage, - connection_type: String::from("webpush"), - host: host.clone(), - ..Default::default() - }, - ..Default::default() - })); - srv.connect_client(RegisteredClient { uaid, uid, tx }); - - let response = ServerMessage::Hello { - uaid: uaid.simple().to_string(), - status: 200, - use_webpush: Some(true), - broadcasts: Service::into_hashmap(broadcasts), - }; - let auth_state_machine = AuthClientState::start( - vec![response], - false, - AuthClientData { - srv: srv.clone(), - ws, - webpush: webpush.clone(), - broadcast_services: broadcast_services.clone(), - }, - ); - transition!(AwaitSessionComplete { - auth_state_machine, - srv, - user_agent, - webpush, - }) - } - - fn poll_await_session_complete<'a>( - session_complete: &'a mut RentToOwn<'a, AwaitSessionComplete>, - ) -> Poll { - // xxx: handle error cases with maybe a log message? - let _error = { - match session_complete.auth_state_machine.poll() { - Ok(Async::Ready(_)) => None, - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(e) => Some(e), - } - }; - - let AwaitSessionComplete { - srv, - user_agent, - webpush, - .. - } = session_complete.take(); - let mut webpush = webpush.borrow_mut(); - // If there's any notifications in the queue, move them to our unacked direct notifs - webpush.rx.close(); - loop { - match webpush.rx.poll() { - Ok(Async::Ready(Some(msg))) => match msg { - ServerNotification::CheckStorage | ServerNotification::Disconnect => continue, - ServerNotification::Notification(notif) => { - webpush.unacked_direct_notifs.push(notif) - } - }, - Ok(Async::Ready(None)) | Ok(Async::NotReady) | Err(_) => break, - } - } - let now = ms_since_epoch(); - let elapsed = (now - webpush.connected_at) / 1_000; - let parser = Parser::new(); - let (ua_result, metrics_os, metrics_browser) = parse_user_agent(&parser, &user_agent); - srv.metrics - .time_with_tags("ua.connection.lifespan", elapsed) - .with_tag("ua_os_family", metrics_os) - .with_tag("ua_browser_family", metrics_browser) - .with_tag("host", &webpush.stats.host) - .send(); - - // If there's direct unack'd messages, they need to be saved out without blocking - // here - srv.disconnet_client(&webpush.uaid, &webpush.uid); - let mut stats = webpush.stats.clone(); - let unacked_direct_notifs = webpush.unacked_direct_notifs.len(); - if unacked_direct_notifs > 0 { - debug!("Writing direct notifications to storage"); - stats.direct_storage += unacked_direct_notifs as i32; - let mut notifs = mem::replace(&mut webpush.unacked_direct_notifs, Vec::new()); - // Ensure we don't store these as legacy by setting a 0 as the sortkey_timestamp - // That will ensure the Python side doesn't mark it as legacy during conversion and - // still get the correct default us_time when saving. - for notif in &mut notifs { - notif.sortkey_timestamp = Some(0); - } - srv.handle.spawn( - srv.ddb - .store_messages(&webpush.uaid, &webpush.message_month, notifs) - .then(|_| { - debug!("Finished saving unacked direct notifications"); - Ok(()) - }), - ); - } - - // Log out the final stats message - info!("Session"; - "uaid_hash" => &stats.uaid, - "uaid_reset" => stats.uaid_reset, - "existing_uaid" => stats.existing_uaid, - "connection_type" => &stats.connection_type, - "host" => &stats.host, - "ua_name" => ua_result.name, - "ua_os_family" => ua_result.os, - "ua_os_ver" => ua_result.os_version, - "ua_browser_family" => ua_result.vendor, - "ua_browser_ver" => ua_result.version, - "ua_category" => ua_result.category, - "connection_time" => elapsed, - "direct_acked" => stats.direct_acked, - "direct_storage" => stats.direct_storage, - "stored_retrieved" => stats.stored_retrieved, - "stored_acked" => stats.stored_acked, - "nacks" => stats.nacks, - "registers" => stats.registers, - "unregisters" => stats.unregisters, - ); - transition!(UnAuthDone(())) - } -} - -#[derive(StateMachineFuture)] -pub enum AuthClientState -where - T: Stream - + Sink - + 'static, -{ - #[state_machine_future(start, transitions(DetermineAck, SendThenWait))] - SendThenWait { - remaining_data: Vec, - poll_complete: bool, - data: AuthClientData, - }, - - #[state_machine_future(transitions(IncrementStorage, CheckStorage, AwaitDropUser, - AwaitMigrateUser, AwaitInput))] - DetermineAck { data: AuthClientData }, - - #[state_machine_future(transitions(DetermineAck, SendThenWait, AwaitInput, AwaitRegister, - AwaitUnregister, AwaitDelete))] - AwaitInput { data: AuthClientData }, - - #[state_machine_future(transitions(AwaitIncrementStorage))] - IncrementStorage { data: AuthClientData }, - - #[state_machine_future(transitions(DetermineAck))] - AwaitIncrementStorage { - response: MyFuture, - data: AuthClientData, - }, - - #[state_machine_future(transitions(AwaitCheckStorage))] - CheckStorage { data: AuthClientData }, - - #[state_machine_future(transitions(SendThenWait, DetermineAck))] - AwaitCheckStorage { - response: MyFuture, - data: AuthClientData, - }, - - #[state_machine_future(transitions(DetermineAck))] - AwaitMigrateUser { - response: MyFuture<()>, - data: AuthClientData, - }, - - #[state_machine_future(transitions(AuthDone))] - AwaitDropUser { - response: MyFuture<()>, - data: AuthClientData, - }, - - #[state_machine_future(transitions(SendThenWait))] - AwaitRegister { - channel_id: Uuid, - response: MyFuture, - data: AuthClientData, - }, - - #[state_machine_future(transitions(SendThenWait))] - AwaitUnregister { - channel_id: Uuid, - code: u32, - response: MyFuture, - data: AuthClientData, - }, - - #[state_machine_future(transitions(DetermineAck))] - AwaitDelete { - response: MyFuture<()>, - data: AuthClientData, - }, - - #[state_machine_future(ready)] - AuthDone(()), - - #[state_machine_future(error)] - AuthClientStateError(Error), -} - -impl PollAuthClientState for AuthClientState -where - T: Stream - + Sink - + 'static, -{ - fn poll_send_then_wait<'a>( - send: &'a mut RentToOwn<'a, SendThenWait>, - ) -> Poll, Error> { - trace!("State: SendThenWait"); - let start_send = { - let SendThenWait { - ref mut remaining_data, - poll_complete, - ref mut data, - .. - } = **send; - if poll_complete { - try_ready!(data.ws.poll_complete()); - false - } else if !remaining_data.is_empty() { - let item = remaining_data.remove(0); - let ret = data.ws.start_send(item).chain_err(|| "unable to send")?; - match ret { - AsyncSink::Ready => true, - AsyncSink::NotReady(returned) => { - remaining_data.insert(0, returned); - return Ok(Async::NotReady); - } - } - } else { - false - } - }; - - let SendThenWait { - data, - remaining_data, - .. - } = send.take(); - if start_send { - transition!(SendThenWait { - remaining_data, - poll_complete: true, - data, - }); - } else if !remaining_data.is_empty() { - transition!(SendThenWait { - remaining_data, - poll_complete: false, - data, - }); - } - transition!(DetermineAck { data }) - } - - fn poll_determine_ack<'a>( - detack: &'a mut RentToOwn<'a, DetermineAck>, - ) -> Poll, Error> { - let DetermineAck { data } = detack.take(); - let webpush_rc = data.webpush.clone(); - let webpush = webpush_rc.borrow(); - let all_acked = !webpush.unacked_messages(); - if all_acked && webpush.flags.check && webpush.flags.increment_storage { - transition!(IncrementStorage { data }); - } else if all_acked && webpush.flags.check { - transition!(CheckStorage { data }); - } else if all_acked && webpush.flags.rotate_message_table { - debug!("Triggering migration"); - let response = Box::new(data.srv.ddb.migrate_user( - &webpush.uaid, - &webpush.message_month, - &data.srv.opts.current_message_month, - &data.srv.opts.router_table_name, - )); - transition!(AwaitMigrateUser { response, data }); - } else if all_acked && webpush.flags.reset_uaid { - let response = Box::new( - data.srv - .ddb - .drop_uaid(&data.srv.opts.router_table_name, &webpush.uaid) - ); - transition!(AwaitDropUser { response, data }); - } - transition!(AwaitInput { data }) - } - - fn poll_await_input<'a>( - await: &'a mut RentToOwn<'a, AwaitInput>, - ) -> Poll, Error> { - trace!("State: AwaitInput"); - let input = try_ready!(await.data.input_or_notif()); - let AwaitInput { data } = await.take(); - let webpush_rc = data.webpush.clone(); - let mut webpush = webpush_rc.borrow_mut(); - match input { - Either::A(ClientMessage::BroadcastSubscribe { broadcasts }) => { - let service_delta = { - let mut broadcast_services = data.broadcast_services.borrow_mut(); - data.srv.client_service_add_service( - &mut broadcast_services, - &Service::from_hashmap(broadcasts), - ) - }; - if let Some(delta) = service_delta { - transition!(SendThenWait { - remaining_data: vec![ - ServerMessage::Broadcast { - broadcasts: Service::into_hashmap(delta), - }, - ], - poll_complete: false, - data, - }); - } else { - transition!(AwaitInput { data }); - } - } - Either::A(ClientMessage::Register { - channel_id: channel_id_str, - key, - }) => { - debug!("Got a register command"; - "channel_id" => &channel_id_str); - let channel_id = - Uuid::parse_str(&channel_id_str).chain_err(|| "Invalid channelID")?; - if channel_id.hyphenated().to_string() != channel_id_str { - return Err("Bad UUID format, use lower case, dashed format".into()); - } - - let uaid = webpush.uaid; - let message_month = webpush.message_month.clone(); - let srv = data.srv.clone(); - let fut = data.srv - .ddb - .register(&srv, &uaid, &channel_id, &message_month, key); - transition!(AwaitRegister { - channel_id, - response: fut, - data, - }); - } - Either::A(ClientMessage::Unregister { channel_id, code }) => { - debug!("Got a unregister command"); - // XXX: unregister should check the format of channel_id like - // register does - let uaid = webpush.uaid; - let message_month = webpush.message_month.clone(); - let response = - Box::new(data.srv.ddb.unregister(&uaid, &channel_id, &message_month)); - transition!(AwaitUnregister { - channel_id, - code: code.unwrap_or(200), - response, - data, - }); - } - Either::A(ClientMessage::Nack { .. }) => { - data.srv.metrics.incr("ua.command.nack").ok(); - webpush.stats.nacks += 1; - transition!(AwaitInput { data }); - } - Either::A(ClientMessage::Ack { updates }) => { - data.srv.metrics.incr("ua.command.ack").ok(); - let mut fut: Option> = None; - for notif in &updates { - if let Some(pos) = webpush.unacked_direct_notifs.iter().position(|v| { - v.channel_id == notif.channel_id && v.version == notif.version - }) { - webpush.stats.direct_acked += 1; - webpush.unacked_direct_notifs.remove(pos); - continue; - }; - if let Some(pos) = webpush.unacked_stored_notifs.iter().position(|v| { - v.channel_id == notif.channel_id && v.version == notif.version - }) { - webpush.stats.stored_acked += 1; - let message_month = webpush.message_month.clone(); - let n = webpush.unacked_stored_notifs.remove(pos); - // Topic/legacy messages have no sortkey_timestamp - if n.sortkey_timestamp.is_none() { - fut = if let Some(call) = fut { - let my_fut = - data.srv - .ddb - .delete_message(&message_month, &webpush.uaid, &n); - Some(Box::new(call.and_then(move |_| my_fut))) - } else { - Some(Box::new(data.srv.ddb.delete_message( - &message_month, - &webpush.uaid, - &n, - ))) - } - } - continue; - }; - } - if let Some(my_fut) = fut { - transition!(AwaitDelete { - response: my_fut, - data, - }); - } else { - transition!(DetermineAck { data }); - } - } - Either::B(ServerNotification::Notification(notif)) => { - if notif.ttl != 0 { - webpush.unacked_direct_notifs.push(notif.clone()); - } - debug!("Got a notification to send, sending!"); - transition!(SendThenWait { - remaining_data: vec![ServerMessage::Notification(notif)], - poll_complete: false, - data, - }); - } - Either::B(ServerNotification::CheckStorage) => { - webpush.flags.include_topic = true; - webpush.flags.check = true; - transition!(DetermineAck { data }); - } - Either::B(ServerNotification::Disconnect) => { - debug!("Got told to disconnect, connecting client has our uaid"); - Err("Repeat UAID disconnect".into()) - } - _ => Err("Invalid message".into()), - } - } - - fn poll_increment_storage<'a>( - increment_storage: &'a mut RentToOwn<'a, IncrementStorage>, - ) -> Poll, Error> { - trace!("State: IncrementStorage"); - let webpush_rc = increment_storage.data.webpush.clone(); - let webpush = webpush_rc.borrow(); - let timestamp = webpush - .unacked_stored_highest - .ok_or("unacked_stored_highest unset")? - .to_string(); - let response = Box::new(increment_storage.data.srv.ddb.increment_storage( - &webpush.message_month, - &webpush.uaid, - ×tamp, - )); - transition!(AwaitIncrementStorage { - response, - data: increment_storage.take().data, - }) - } - - fn poll_await_increment_storage<'a>( - await_increment_storage: &'a mut RentToOwn<'a, AwaitIncrementStorage>, - ) -> Poll, Error> { - trace!("State: AwaitIncrementStorage"); - try_ready!(await_increment_storage.response.poll()); - let AwaitIncrementStorage { data, .. } = await_increment_storage.take(); - let webpush = data.webpush.clone(); - webpush.borrow_mut().flags.increment_storage = false; - transition!(DetermineAck { data }) - } - - fn poll_check_storage<'a>( - check_storage: &'a mut RentToOwn<'a, CheckStorage>, - ) -> Poll, Error> { - trace!("State: CheckStorage"); - let CheckStorage { data } = check_storage.take(); - let response = Box::new({ - let webpush = data.webpush.borrow(); - data.srv.ddb.check_storage( - &webpush.message_month.clone(), - &webpush.uaid, - webpush.flags.include_topic, - webpush.unacked_stored_highest, - ) - }); - transition!(AwaitCheckStorage { response, data }) - } - - fn poll_await_check_storage<'a>( - await_check_storage: &'a mut RentToOwn<'a, AwaitCheckStorage>, - ) -> Poll, Error> { - trace!("State: AwaitCheckStorage"); - let (include_topic, mut messages, timestamp) = - match try_ready!(await_check_storage.response.poll()) { - CheckStorageResponse { - include_topic, - messages, - timestamp, - } => (include_topic, messages, timestamp), - }; - debug!("Got checkstorage response"); - - let AwaitCheckStorage { data, .. } = await_check_storage.take(); - let webpush_rc = data.webpush.clone(); - let mut webpush = webpush_rc.borrow_mut(); - webpush.flags.include_topic = include_topic; - debug!("Setting unacked stored highest to {:?}", timestamp); - webpush.unacked_stored_highest = timestamp; - if !messages.is_empty() { - // Filter out TTL expired messages - let now = sec_since_epoch() as u32; - messages.retain(|ref msg| now < msg.ttl + msg.timestamp); - webpush.flags.increment_storage = !include_topic && timestamp.is_some(); - // If there's still messages send them out - if !messages.is_empty() { - webpush - .unacked_stored_notifs - .extend(messages.iter().cloned()); - transition!(SendThenWait { - remaining_data: messages - .into_iter() - .map(ServerMessage::Notification) - .collect(), - poll_complete: false, - data, - }) - } else { - // No messages remaining - transition!(DetermineAck { data }) - } - } else { - webpush.flags.check = false; - transition!(DetermineAck { data }) - } - } - - fn poll_await_migrate_user<'a>( - await_migrate_user: &'a mut RentToOwn<'a, AwaitMigrateUser>, - ) -> Poll, Error> { - trace!("State: AwaitMigrateUser"); - try_ready!(await_migrate_user.response.poll()); - let AwaitMigrateUser { data, .. } = await_migrate_user.take(); - { - let mut webpush = data.webpush.borrow_mut(); - webpush.message_month = data.srv.opts.current_message_month.clone(); - webpush.flags.rotate_message_table = false; - } - transition!(DetermineAck { data }) - } - - fn poll_await_drop_user<'a>( - await_drop_user: &'a mut RentToOwn<'a, AwaitDropUser>, - ) -> Poll { - trace!("State: AwaitDropUser"); - try_ready!(await_drop_user.response.poll()); - transition!(AuthDone(())) - } - - fn poll_await_register<'a>( - await_register: &'a mut RentToOwn<'a, AwaitRegister>, - ) -> Poll, Error> { - trace!("State: AwaitRegister"); - let msg = match try_ready!(await_register.response.poll()) { - RegisterResponse::Success { endpoint } => { - let mut webpush = await_register.data.webpush.borrow_mut(); - await_register - .data - .srv - .metrics - .incr("ua.command.register") - .ok(); - webpush.stats.registers += 1; - ServerMessage::Register { - channel_id: await_register.channel_id, - status: 200, - push_endpoint: endpoint, - } - } - RegisterResponse::Error { error_msg, status } => { - debug!("Got unregister fail, error: {}", error_msg); - ServerMessage::Register { - channel_id: await_register.channel_id, - status, - push_endpoint: "".into(), - } - } - }; - - transition!(SendThenWait { - remaining_data: vec![msg], - poll_complete: false, - data: await_register.take().data, - }) - } - - fn poll_await_unregister<'a>( - await_unregister: &'a mut RentToOwn<'a, AwaitUnregister>, - ) -> Poll, Error> { - trace!("State: AwaitUnRegister"); - let msg = if try_ready!(await_unregister.response.poll()) { - debug!("Got the unregister response"); - let mut webpush = await_unregister.data.webpush.borrow_mut(); - webpush.stats.unregisters += 1; - ServerMessage::Unregister { - channel_id: await_unregister.channel_id, - status: 200, - } - } else { - debug!("Got unregister fail"); - ServerMessage::Unregister { - channel_id: await_unregister.channel_id, - status: 500, - } - }; - - let AwaitUnregister { code, data, .. } = await_unregister.take(); - data.srv - .metrics - .incr_with_tags("ua.command.unregister") - .with_tag("code", &code.to_string()) - .send(); - transition!(SendThenWait { - remaining_data: vec![msg], - poll_complete: false, - data - }) - } - - fn poll_await_delete<'a>( - await_delete: &'a mut RentToOwn<'a, AwaitDelete>, - ) -> Poll, Error> { - trace!("State: AwaitDelete"); - try_ready!(await_delete.response.poll()); - transition!(DetermineAck { - data: await_delete.take().data, - }) - } -} diff --git a/autopush_rs/src/db/commands.rs b/autopush_rs/src/db/commands.rs deleted file mode 100644 index ead1a21d..00000000 --- a/autopush_rs/src/db/commands.rs +++ /dev/null @@ -1,416 +0,0 @@ -use std::collections::HashSet; -use std::rc::Rc; -use std::result::Result as StdResult; -use uuid::Uuid; - -use cadence::{Counted, StatsdClient}; -use chrono::Utc; -use futures::{future, Future}; -use futures_backoff::retry_if; -use rusoto_dynamodb::{ - AttributeValue, DeleteItemError, DeleteItemInput, DeleteItemOutput, DynamoDb, GetItemError, - GetItemInput, GetItemOutput, ListTablesInput, ListTablesOutput, PutItemError, PutItemInput, - PutItemOutput, QueryError, QueryInput, UpdateItemError, UpdateItemInput, UpdateItemOutput, -}; -use serde_dynamodb; - -use super::models::{DynamoDbNotification, DynamoDbUser}; -use super::util::generate_last_connect; -use super::{HelloResponse, MAX_EXPIRY, USER_RECORD_VERSION}; -use errors::*; -use protocol::Notification; -use util::timing::sec_since_epoch; - -#[derive(Default)] -pub struct FetchMessageResponse { - pub timestamp: Option, - pub messages: Vec, -} - -/// Indicate whether this last_connect falls in the current month -fn has_connected_this_month(user: &DynamoDbUser) -> bool { - user.last_connect.map_or(false, |v| { - let pat = Utc::now().format("%Y%m").to_string(); - v.to_string().starts_with(&pat) - }) -} - -pub fn list_tables( - ddb: Rc>, - start_key: Option, -) -> impl Future { - let input = ListTablesInput { - exclusive_start_table_name: start_key, - limit: Some(100), - }; - ddb.list_tables(&input) - .chain_err(|| "Unable to list tables") -} - -pub fn fetch_messages( - ddb: Rc>, - table_name: &str, - uaid: &Uuid, - limit: u32, -) -> impl Future { - let attr_values = hashmap! { - ":uaid".to_string() => val!(S => uaid.simple().to_string()), - ":cmi".to_string() => val!(S => "02"), - }; - let input = QueryInput { - key_condition_expression: Some("uaid = :uaid AND chidmessageid < :cmi".to_string()), - expression_attribute_values: Some(attr_values), - table_name: table_name.to_string(), - consistent_read: Some(true), - limit: Some(limit as i64), - ..Default::default() - }; - - let cond = |err: &QueryError| matches!(err, &QueryError::ProvisionedThroughputExceeded(_)); - retry_if(move || ddb.query(&input), cond) - .chain_err(|| "Error fetching messages") - .and_then(|output| { - let mut notifs: Vec = - output.items.map_or_else(Vec::new, |items| { - debug!("Got response of: {:?}", items); - // TODO: Capture translation errors and report them as we shouldn't - // have corrupt data - items - .into_iter() - .inspect(|i| debug!("Item: {:?}", i)) - .filter_map(|item| serde_dynamodb::from_hashmap(item).ok()) - .collect() - }); - if notifs.is_empty() { - return Ok(Default::default()); - } - - // Load the current_timestamp from the subscription registry entry which is - // the first DynamoDbNotification and remove it from the vec. - let timestamp = notifs.remove(0).current_timestamp; - // Convert any remaining DynamoDbNotifications to Notification's - // TODO: Capture translation errors and report them as we shouldn't have corrupt data - let messages = notifs - .into_iter() - .filter_map(|ddb_notif| ddb_notif.into_notif().ok()) - .collect(); - Ok(FetchMessageResponse { - timestamp, - messages, - }) - }) -} - -pub fn fetch_timestamp_messages( - ddb: Rc>, - table_name: &str, - uaid: &Uuid, - timestamp: Option, - limit: u32, -) -> impl Future { - let range_key = if let Some(ts) = timestamp { - format!("02:{}:z", ts) - } else { - "01;".to_string() - }; - let attr_values = hashmap! { - ":uaid".to_string() => val!(S => uaid.simple().to_string()), - ":cmi".to_string() => val!(S => range_key), - }; - let input = QueryInput { - key_condition_expression: Some("uaid = :uaid AND chidmessageid > :cmi".to_string()), - expression_attribute_values: Some(attr_values), - table_name: table_name.to_string(), - consistent_read: Some(true), - limit: Some(limit as i64), - ..Default::default() - }; - - let cond = |err: &QueryError| matches!(err, &QueryError::ProvisionedThroughputExceeded(_)); - retry_if(move || ddb.query(&input), cond) - .chain_err(|| "Error fetching messages") - .and_then(|output| { - let messages = output.items.map_or_else(Vec::new, |items| { - debug!("Got response of: {:?}", items); - // TODO: Capture translation errors and report them as we shouldn't have corrupt data - items - .into_iter() - .filter_map(|item| serde_dynamodb::from_hashmap(item).ok()) - .filter_map(|ddb_notif: DynamoDbNotification| ddb_notif.into_notif().ok()) - .collect() - }); - let timestamp = messages.iter().filter_map(|m| m.sortkey_timestamp).max(); - Ok(FetchMessageResponse { - timestamp, - messages, - }) - }) -} - -pub fn drop_user( - ddb: Rc>, - uaid: &Uuid, - router_table_name: &str, -) -> impl Future { - let input = DeleteItemInput { - table_name: router_table_name.to_string(), - key: ddb_item! { uaid: s => uaid.simple().to_string() }, - ..Default::default() - }; - retry_if( - move || ddb.delete_item(&input), - |err: &DeleteItemError| matches!(err, &DeleteItemError::ProvisionedThroughputExceeded(_)), - ).chain_err(|| "Error dropping user") -} - -fn get_uaid( - ddb: Rc>, - uaid: &Uuid, - router_table_name: &str, -) -> impl Future { - let input = GetItemInput { - table_name: router_table_name.to_string(), - consistent_read: Some(true), - key: ddb_item! { uaid: s => uaid.simple().to_string() }, - ..Default::default() - }; - retry_if( - move || ddb.get_item(&input), - |err: &GetItemError| matches!(err, &GetItemError::ProvisionedThroughputExceeded(_)), - ).chain_err(|| "Error fetching user") -} - -pub fn register_user( - ddb: Rc>, - user: &DynamoDbUser, - router_table: &str, -) -> impl Future { - let item = match serde_dynamodb::to_hashmap(user) { - Ok(item) => item, - Err(e) => return future::err(e).chain_err(|| "Failed to serialize item"), - }; - let router_table = router_table.to_string(); - let attr_values = hashmap! { - ":router_type".to_string() => val!(S => user.router_type), - ":connected_at".to_string() => val!(N => user.connected_at), - }; - - retry_if( - move || { - debug!("Registering user: {:?}", item); - ddb.put_item(&PutItemInput { - item: item.clone(), - table_name: router_table.clone(), - expression_attribute_values: Some(attr_values.clone()), - condition_expression: Some( - r#"( - attribute_not_exists(router_type) or - (router_type = :router_type) - ) and ( - attribute_not_exists(node_id) or - (connected_at < :connected_at) - )"#.to_string(), - ), - return_values: Some("ALL_OLD".to_string()), - ..Default::default() - }) - }, - |err: &PutItemError| matches!(err, &PutItemError::ProvisionedThroughputExceeded(_)), - ).chain_err(|| "Error storing user record") -} - -pub fn update_user_message_month( - ddb: Rc>, - uaid: &Uuid, - router_table_name: &str, - message_month: &str, -) -> impl Future { - let attr_values = hashmap! { - ":curmonth".to_string() => val!(S => message_month.to_string()), - ":lastconnect".to_string() => val!(N => generate_last_connect().to_string()), - }; - let update_item = UpdateItemInput { - key: ddb_item! { uaid: s => uaid.simple().to_string() }, - update_expression: Some( - "SET current_month=:curmonth, last_connect=:lastconnect".to_string(), - ), - expression_attribute_values: Some(attr_values), - table_name: router_table_name.to_string(), - ..Default::default() - }; - - retry_if( - move || ddb.update_item(&update_item).and_then(|_| future::ok(())), - |err: &UpdateItemError| matches!(err, &UpdateItemError::ProvisionedThroughputExceeded(_)), - ).chain_err(|| "Error updating user message month") -} - -pub fn all_channels( - ddb: Rc>, - uaid: &Uuid, - message_table_name: &str, -) -> impl Future, Error = Error> { - let input = GetItemInput { - table_name: message_table_name.to_string(), - consistent_read: Some(true), - key: ddb_item! { - uaid: s => uaid.simple().to_string(), - chidmessageid: s => " ".to_string() - }, - ..Default::default() - }; - - let cond = |err: &GetItemError| matches!(err, &GetItemError::ProvisionedThroughputExceeded(_)); - retry_if(move || ddb.get_item(&input), cond) - .and_then(|output| { - let channels = output - .item - .and_then(|item| { - serde_dynamodb::from_hashmap::(item) - .ok() - .and_then(|notif| notif.chids) - }) - .unwrap_or_else(HashSet::new); - future::ok(channels) - }) - .or_else(|_err| future::ok(HashSet::new())) -} - -pub fn save_channels( - ddb: Rc>, - uaid: &Uuid, - channels: HashSet, - message_table_name: &str, -) -> impl Future { - let chids: Vec = channels.into_iter().collect(); - let expiry = sec_since_epoch() + 2 * MAX_EXPIRY; - let attr_values = hashmap! { - ":chids".to_string() => val!(SS => chids), - ":expiry".to_string() => val!(N => expiry), - }; - let update_item = UpdateItemInput { - key: ddb_item! { - uaid: s => uaid.simple().to_string(), - chidmessageid: s => " ".to_string() - }, - update_expression: Some("ADD chids :chids SET expiry=:expiry".to_string()), - expression_attribute_values: Some(attr_values), - table_name: message_table_name.to_string(), - ..Default::default() - }; - - retry_if( - move || ddb.update_item(&update_item).and_then(|_| future::ok(())), - |err: &UpdateItemError| matches!(err, &UpdateItemError::ProvisionedThroughputExceeded(_)), - ).chain_err(|| "Error saving channels") -} - -pub fn unregister_channel_id( - ddb: Rc>, - uaid: &Uuid, - channel_id: &Uuid, - message_table_name: &str, -) -> impl Future { - let chid = channel_id.hyphenated().to_string(); - let attr_values = hashmap! { - ":channel_id".to_string() => val!(SS => vec![chid]), - }; - let update_item = UpdateItemInput { - key: ddb_item! { - uaid: s => uaid.simple().to_string(), - chidmessageid: s => " ".to_string() - }, - update_expression: Some("DELETE chids :channel_id".to_string()), - expression_attribute_values: Some(attr_values), - table_name: message_table_name.to_string(), - ..Default::default() - }; - - retry_if( - move || ddb.update_item(&update_item), - |err: &UpdateItemError| matches!(err, &UpdateItemError::ProvisionedThroughputExceeded(_)), - ).chain_err(|| "Error unregistering channel") -} - -pub fn lookup_user( - ddb: Rc>, - uaid: &Uuid, - connected_at: &u64, - router_url: &str, - router_table_name: &str, - message_table_names: &[String], - current_message_month: &str, - metrics: &StatsdClient, -) -> MyFuture<(HelloResponse, Option)> { - let response = get_uaid(ddb.clone(), uaid, router_table_name); - // Prep all these for the move into the static closure capture - let cur_month = current_message_month.to_string(); - let uaid2 = *uaid; - let router_table = router_table_name.to_string(); - let messages_tables = message_table_names.to_vec(); - let connected_at = *connected_at; - let router_url = router_url.to_string(); - let metrics = metrics.clone(); - let response = response.and_then(move |data| -> MyFuture<_> { - let mut hello_response: HelloResponse = Default::default(); - hello_response.message_month = cur_month.clone(); - let user = handle_user_result( - &cur_month, - &messages_tables, - connected_at, - router_url, - data, - &mut hello_response, - ); - match user { - Ok(user) => Box::new(future::ok((hello_response, Some(user)))), - Err((false, _)) => Box::new(future::ok((hello_response, None))), - Err((true, code)) => { - metrics - .incr_with_tags("ua.expiration") - .with_tag("code", &code.to_string()) - .send(); - let response = drop_user(ddb, &uaid2, &router_table) - .and_then(|_| future::ok((hello_response, None))) - .chain_err(|| "Unable to drop user"); - Box::new(response) - } - } - }); - Box::new(response) -} - -/// Helper function for determining if a returned user record is valid for use -/// or if it should be dropped and a new one created. -fn handle_user_result( - cur_month: &String, - messages_tables: &[String], - connected_at: u64, - router_url: String, - data: GetItemOutput, - hello_response: &mut HelloResponse, -) -> StdResult { - let item = data.item.ok_or((false, 104))?; - let mut user: DynamoDbUser = serde_dynamodb::from_hashmap(item).map_err(|_| (true, 104))?; - - let user_month = user.current_month.clone(); - let month = user_month.ok_or((true, 104))?; - if !messages_tables.contains(cur_month) { - return Err((true, 105)); - } - hello_response.check_storage = true; - hello_response.message_month = month.clone(); - hello_response.rotate_message_table = *cur_month != month; - hello_response.reset_uaid = user - .record_version - .map_or(true, |rec_ver| rec_ver < USER_RECORD_VERSION); - - user.last_connect = if has_connected_this_month(&user) { - None - } else { - Some(generate_last_connect()) - }; - user.node_id = Some(router_url); - user.connected_at = connected_at; - Ok(user) -} diff --git a/autopush_rs/src/db/macros.rs b/autopush_rs/src/db/macros.rs deleted file mode 100644 index 3e716ddf..00000000 --- a/autopush_rs/src/db/macros.rs +++ /dev/null @@ -1,109 +0,0 @@ -/// A bunch of macro helpers from rusoto_helpers code, which they pulled from crates.io because -/// they were waiting for rusuto to hit 1.0.0 or something. For sanity, they are instead accumulated -/// here for our use. -#[allow(unused_macros)] -macro_rules! attributes { - ($($val:expr => $attr_type:expr),*) => { - { - let mut temp_vec = Vec::new(); - $( - temp_vec.push(AttributeDefinition { - attribute_name: String::from($val), - attribute_type: String::from($attr_type) - }); - )* - temp_vec - } - } -} - -#[allow(unused_macros)] -macro_rules! key_schema { - ($($name:expr => $key_type:expr),*) => { - { - let mut temp_vec = Vec::new(); - $( - temp_vec.push(KeySchemaElement { - key_type: String::from($key_type), - attribute_name: String::from($name) - }); - )* - temp_vec - } - } -} - -macro_rules! val { - (B => $val:expr) => {{ - let mut attr = AttributeValue::default(); - attr.b = Some($val); - attr - }}; - (S => $val:expr) => {{ - let mut attr = AttributeValue::default(); - attr.s = Some($val.to_string()); - attr - }}; - (SS => $val:expr) => {{ - let mut attr = AttributeValue::default(); - let vals: Vec = $val.iter().map(|v| v.to_string()).collect(); - attr.ss = Some(vals); - attr - }}; - (N => $val:expr) => {{ - let mut attr = AttributeValue::default(); - attr.n = Some($val.to_string()); - attr - }}; -} - -/// Create a **HashMap** from a list of key-value pairs -/// -/// ## Example -/// -/// ``` -/// #[macro_use] extern crate rusoto_helpers; -/// # fn main() { -/// -/// let map = hashmap!{ -/// "a" => 1, -/// "b" => 2, -/// }; -/// assert_eq!(map["a"], 1); -/// assert_eq!(map["b"], 2); -/// assert_eq!(map.get("c"), None); -/// # } -/// ``` -macro_rules! hashmap { - (@single $($x:tt)*) => (()); - (@count $($rest:expr),*) => (<[()]>::len(&[$(hashmap!(@single $rest)),*])); - - ($($key:expr => $value:expr,)+) => { hashmap!($($key => $value),+) }; - ($($key:expr => $value:expr),*) => { - { - let _cap = hashmap!(@count $($key),*); - let mut _map = ::std::collections::HashMap::with_capacity(_cap); - $( - _map.insert($key, $value); - )* - _map - } - }; -} - -/// Shorthand for specifying a dynamodb item -macro_rules! ddb_item { - ($($p:tt: $t:tt => $x:expr),*) => { - { - use rusoto_dynamodb::AttributeValue; - hashmap!{ - $( - String::from(stringify!($p)) => AttributeValue { - $t: Some($x), - ..Default::default() - }, - )* - } - } - } -} diff --git a/autopush_rs/src/db/mod.rs b/autopush_rs/src/db/mod.rs deleted file mode 100644 index e003bc23..00000000 --- a/autopush_rs/src/db/mod.rs +++ /dev/null @@ -1,405 +0,0 @@ -use std::collections::HashSet; -use std::env; -use std::rc::Rc; -use uuid::Uuid; - -use cadence::StatsdClient; -use futures::{future, Future}; -use futures_backoff::retry_if; -use rusoto_core::reactor::RequestDispatcher; -use rusoto_core::Region; -use rusoto_credential::StaticProvider; -use rusoto_dynamodb::{ - AttributeValue, BatchWriteItemError, BatchWriteItemInput, DeleteItemError, DeleteItemInput, - DynamoDb, DynamoDbClient, PutRequest, UpdateItemError, UpdateItemInput, UpdateItemOutput, - WriteRequest, -}; -use serde_dynamodb; - -#[macro_use] -mod macros; -mod commands; -mod models; -use errors::*; -use protocol::Notification; -use server::Server; -mod util; -use util::timing::sec_since_epoch; - -use self::commands::FetchMessageResponse; -use self::models::{DynamoDbNotification, DynamoDbUser}; - -const MAX_EXPIRY: u64 = 2_592_000; -const USER_RECORD_VERSION: u8 = 1; - -/// Basic requirements for notification content to deliver to websocket client -/// - channelID (the subscription website intended for) -/// - version (only really utilized for notification acknowledgement in -/// webpush, used to be the sole carrier of data, can now be anything) -/// - data (encrypted content) -/// - headers (hash of crypto headers: encoding, encrypption, crypto-key, encryption-key) -#[derive(Default, Clone)] -pub struct HelloResponse { - pub uaid: Option, - pub message_month: String, - pub check_storage: bool, - pub reset_uaid: bool, - pub rotate_message_table: bool, - pub connected_at: u64, -} - -pub struct CheckStorageResponse { - pub include_topic: bool, - pub messages: Vec, - pub timestamp: Option, -} - -pub enum RegisterResponse { - Success { endpoint: String }, - - Error { error_msg: String, status: u32 }, -} - -pub struct DynamoStorage { - ddb: Rc>, -} - -impl DynamoStorage { - pub fn new() -> Self { - let ddb: Box = if let Ok(endpoint) = env::var("AWS_LOCAL_DYNAMODB") { - Box::new(DynamoDbClient::new( - RequestDispatcher::default(), - StaticProvider::new_minimal("BogusKey".to_string(), "BogusKey".to_string()), - Region::Custom { - name: "us-east-1".to_string(), - endpoint, - }, - )) - } else { - Box::new(DynamoDbClient::simple(Region::default())) - }; - Self { ddb: Rc::new(ddb) } - } - - pub fn list_message_tables(&self, prefix: &str) -> Result> { - let mut names: Vec = Vec::new(); - let mut start_key = None; - loop { - let result = commands::list_tables(self.ddb.clone(), start_key).wait()?; - start_key = result.last_evaluated_table_name; - if let Some(table_names) = result.table_names { - names.extend(table_names); - } - if start_key.is_none() { - break; - } - } - let names = names - .into_iter() - .filter(|name| name.starts_with(prefix)) - .collect(); - Ok(names) - } - - pub fn increment_storage( - &self, - table_name: &str, - uaid: &Uuid, - timestamp: &str, - ) -> impl Future { - let ddb = self.ddb.clone(); - let expiry = sec_since_epoch() + 2 * MAX_EXPIRY; - let attr_values = hashmap! { - ":timestamp".to_string() => val!(N => timestamp), - ":expiry".to_string() => val!(N => expiry), - }; - let update_input = UpdateItemInput { - key: ddb_item! { - uaid: s => uaid.simple().to_string(), - chidmessageid: s => " ".to_string() - }, - update_expression: Some("SET current_timestamp=:timestamp, expiry=:expiry".to_string()), - expression_attribute_values: Some(attr_values), - table_name: table_name.to_string(), - ..Default::default() - }; - - retry_if( - move || ddb.update_item(&update_input), - |err: &UpdateItemError| { - matches!(err, &UpdateItemError::ProvisionedThroughputExceeded(_)) - }, - ).chain_err(|| "Error incrementing storage") - } - - pub fn hello( - &self, - connected_at: &u64, - uaid: Option<&Uuid>, - router_table_name: &str, - router_url: &str, - message_table_names: &[String], - current_message_month: &str, - metrics: &StatsdClient, - ) -> impl Future { - let router_table_name = router_table_name.to_string(); - let response: MyFuture<(HelloResponse, Option)> = if let Some(uaid) = uaid { - commands::lookup_user( - self.ddb.clone(), - &uaid, - connected_at, - router_url, - &router_table_name, - message_table_names, - current_message_month, - metrics, - ) - } else { - Box::new(future::ok(( - HelloResponse { - message_month: current_message_month.to_string(), - ..Default::default() - }, - None, - ))) - }; - let ddb = self.ddb.clone(); - let router_url = router_url.to_string(); - let connected_at = *connected_at; - - response.and_then(move |(mut hello_response, user_opt)| { - let hello_message_month = hello_response.message_month.clone(); - let user = user_opt.unwrap_or_else(|| DynamoDbUser { - current_month: Some(hello_message_month), - node_id: Some(router_url), - connected_at, - ..Default::default() - }); - let uaid = user.uaid; - let mut err_response = hello_response.clone(); - err_response.connected_at = connected_at; - commands::register_user(ddb, &user, router_table_name.as_ref()) - .and_then(move |result| { - debug!("Success adding user, item output: {:?}", result); - hello_response.uaid = Some(uaid); - future::ok(hello_response) - }) - .or_else(move |e| { - debug!("Error registering user: {:?}", e); - future::ok(err_response) - }) - }) - } - - pub fn register( - &self, - srv: &Rc, - uaid: &Uuid, - channel_id: &Uuid, - message_month: &str, - key: Option, - ) -> MyFuture { - let ddb = self.ddb.clone(); - let endpoint = match srv.make_endpoint(uaid, channel_id, key) { - Ok(result) => result, - Err(_) => { - return Box::new(future::ok(RegisterResponse::Error { - error_msg: "Failed to generate endpoint".to_string(), - status: 400, - })) - } - }; - let mut chids = HashSet::new(); - chids.insert(channel_id.hyphenated().to_string()); - let response = commands::save_channels(ddb, uaid, chids, message_month) - .and_then(move |_| future::ok(RegisterResponse::Success { endpoint })) - .or_else(move |_| { - future::ok(RegisterResponse::Error { - status: 503, - error_msg: "Failed to register channel".to_string(), - }) - }); - Box::new(response) - } - - pub fn drop_uaid( - &self, - table_name: &str, - uaid: &Uuid, - ) -> impl Future { - commands::drop_user(self.ddb.clone(), uaid, table_name) - .and_then(|_| future::ok(())) - .chain_err(|| "Unable to drop user record") - } - - pub fn unregister( - &self, - uaid: &Uuid, - channel_id: &Uuid, - message_month: &str, - ) -> impl Future { - commands::unregister_channel_id(self.ddb.clone(), uaid, channel_id, message_month) - .and_then(|_| future::ok(true)) - .or_else(|_| future::ok(false)) - } - - /// Migrate a user to a new month table - pub fn migrate_user( - &self, - uaid: &Uuid, - message_month: &str, - current_message_month: &str, - router_table_name: &str, - ) -> impl Future { - let uaid = *uaid; - let ddb = self.ddb.clone(); - let ddb2 = self.ddb.clone(); - let cur_month = current_message_month.to_string(); - let cur_month2 = cur_month.clone(); - let router_table_name = router_table_name.to_string(); - - commands::all_channels(self.ddb.clone(), &uaid, message_month) - .and_then(move |channels| -> MyFuture<_> { - if channels.is_empty() { - Box::new(future::ok(())) - } else { - Box::new(commands::save_channels(ddb, &uaid, channels, &cur_month)) - } - }) - .and_then(move |_| { - commands::update_user_message_month(ddb2, &uaid, &router_table_name, &cur_month2) - }) - .and_then(|_| future::ok(())) - .chain_err(|| "Unable to migrate user") - } - - /// Store a batch of messages when shutting down - pub fn store_messages( - &self, - uaid: &Uuid, - message_month: &str, - messages: Vec, - ) -> impl Future { - let ddb = self.ddb.clone(); - let put_items: Vec = messages - .into_iter() - .filter_map(|n| { - serde_dynamodb::to_hashmap(&DynamoDbNotification::from_notif(uaid, n)) - .ok() - .map(|hm| WriteRequest { - put_request: Some(PutRequest { item: hm }), - delete_request: None, - }) - }) - .collect(); - let batch_input = BatchWriteItemInput { - request_items: hashmap! { message_month.to_string() => put_items }, - ..Default::default() - }; - - let cond = |err: &BatchWriteItemError| { - matches!(err, &BatchWriteItemError::ProvisionedThroughputExceeded(_)) - }; - retry_if(move || ddb.batch_write_item(&batch_input), cond) - .and_then(|_| future::ok(())) - .map_err(|err| { - debug!("Error saving notification: {:?}", err); - err - }) - // TODO: Use Sentry to capture/report this error - .chain_err(|| "Error saving notifications") - } - - /// Delete a given notification from the database - /// - /// No checks are done to see that this message came from the database or has - /// sufficient properties for a delete as that is expected to have been done - /// before this is called. - pub fn delete_message( - &self, - table_name: &str, - uaid: &Uuid, - notif: &Notification, - ) -> impl Future { - let ddb = self.ddb.clone(); - let delete_input = DeleteItemInput { - table_name: table_name.to_string(), - key: ddb_item! { - uaid: s => uaid.simple().to_string(), - chidmessageid: s => notif.sort_key() - }, - ..Default::default() - }; - - let cond = |err: &DeleteItemError| { - matches!(err, &DeleteItemError::ProvisionedThroughputExceeded(_)) - }; - retry_if(move || ddb.delete_item(&delete_input), cond) - .and_then(|_| future::ok(())) - .chain_err(|| "Error deleting notification") - } - - pub fn check_storage( - &self, - table_name: &str, - uaid: &Uuid, - include_topic: bool, - timestamp: Option, - ) -> impl Future { - let response: MyFuture = if include_topic { - Box::new(commands::fetch_messages( - self.ddb.clone(), - table_name, - uaid, - 11 as u32, - )) - } else { - Box::new(future::ok(Default::default())) - }; - let uaid = *uaid; - let table_name = table_name.to_string(); - let ddb = self.ddb.clone(); - - response.and_then(move |resp| -> MyFuture<_> { - // Return now from this future if we have messages - if !resp.messages.is_empty() { - debug!("Topic message returns: {:?}", resp.messages); - return Box::new(future::ok(CheckStorageResponse { - include_topic: true, - messages: resp.messages, - timestamp: resp.timestamp, - })); - } - // Use the timestamp returned by the topic query if we were looking at the topics - let timestamp = if include_topic { - resp.timestamp - } else { - timestamp - }; - let next_query: MyFuture<_> = { - if resp.messages.is_empty() || resp.timestamp.is_some() { - Box::new(commands::fetch_timestamp_messages( - ddb, - table_name.as_ref(), - &uaid, - timestamp, - 10 as u32, - )) - } else { - Box::new(future::ok(Default::default())) - } - }; - let next_query = next_query.and_then(move |resp: FetchMessageResponse| { - // If we didn't get a timestamp off the last query, use the original - // value if passed one - let timestamp = resp.timestamp.or(timestamp); - Ok(CheckStorageResponse { - include_topic: false, - messages: resp.messages, - timestamp, - }) - }); - Box::new(next_query) - }) - } -} diff --git a/autopush_rs/src/db/models.rs b/autopush_rs/src/db/models.rs deleted file mode 100644 index 3277e2ad..00000000 --- a/autopush_rs/src/db/models.rs +++ /dev/null @@ -1,275 +0,0 @@ -use std::cmp::min; -use std::collections::{HashMap, HashSet}; -use std::result::Result as StdResult; -use uuid::Uuid; - -use regex::RegexSet; -use serde::Serializer; - -use db::util::generate_last_connect; -use errors::*; -use protocol::Notification; -use util::timing::{ms_since_epoch, sec_since_epoch}; - -use super::{MAX_EXPIRY, USER_RECORD_VERSION}; - -/// Custom Uuid serializer -/// -/// Serializes a Uuid as a simple string instead of hyphenated -fn uuid_serializer(x: &Uuid, s: S) -> StdResult -where - S: Serializer, -{ - s.serialize_str(&x.simple().to_string()) -} - -/// Direct representation of a DynamoDB Notification as we store it in the database -/// Most attributes are optional -#[derive(Default, Deserialize, PartialEq, Debug, Clone, Serialize)] -struct NotificationHeaders { - #[serde(skip_serializing_if = "Option::is_none")] - crypto_key: Option, - #[serde(skip_serializing_if = "Option::is_none")] - encryption: Option, - #[serde(skip_serializing_if = "Option::is_none")] - encryption_key: Option, - #[serde(skip_serializing_if = "Option::is_none")] - encoding: Option, -} - -fn insert_to_map(map: &mut HashMap, name: &str, val: Option) { - if let Some(val) = val { - map.insert(name.to_string(), val); - } -} - -impl From for HashMap { - fn from(val: NotificationHeaders) -> Self { - let mut map = Self::new(); - insert_to_map(&mut map, "crypto_key", val.crypto_key); - insert_to_map(&mut map, "encryption", val.encryption); - insert_to_map(&mut map, "encryption_key", val.encryption_key); - insert_to_map(&mut map, "encoding", val.encoding); - map - } -} - -impl From> for NotificationHeaders { - fn from(val: HashMap) -> Self { - Self { - crypto_key: val.get("crypto_key").map(|v| v.to_string()), - encryption: val.get("encryption").map(|v| v.to_string()), - encryption_key: val.get("encryption_key").map(|v| v.to_string()), - encoding: val.get("encoding").map(|v| v.to_string()), - } - } -} - -#[derive(Deserialize, PartialEq, Debug, Clone, Serialize)] -pub struct DynamoDbUser { - // DynamoDB - #[serde(serialize_with = "uuid_serializer")] - pub uaid: Uuid, - // Time in milliseconds that the user last connected at - pub connected_at: u64, - // Router type of the user - pub router_type: String, - // Keyed time in a month the user last connected at with limited key range for indexing - #[serde(skip_serializing_if = "Option::is_none")] - pub last_connect: Option, - // Last node/port the client was or may be connected to - #[serde(skip_serializing_if = "Option::is_none")] - pub node_id: Option, - // Record version - #[serde(skip_serializing_if = "Option::is_none")] - pub record_version: Option, - // Current month table in the database the user is on - #[serde(skip_serializing_if = "Option::is_none")] - pub current_month: Option, -} - -impl Default for DynamoDbUser { - fn default() -> Self { - Self { - uaid: Uuid::new_v4(), - connected_at: ms_since_epoch(), - router_type: "webpush".to_string(), - last_connect: Some(generate_last_connect()), - node_id: None, - record_version: Some(USER_RECORD_VERSION), - current_month: None, - } - } -} - -#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] -pub struct DynamoDbNotification { - // DynamoDB - #[serde(serialize_with = "uuid_serializer")] - uaid: Uuid, - // DynamoDB - // Format: - // Topic Messages: - // 01:{channel id}:{topic} - // New Messages: - // 02:{timestamp int in microseconds}:{channel id} - chidmessageid: String, - // Magic entry stored in the first Message record that indicates the highest - // non-topic timestamp we've read into - #[serde(skip_serializing_if = "Option::is_none")] - pub current_timestamp: Option, - // Magic entry stored in the first Message record that indicates the valid - // channel id's - #[serde(skip_serializing)] - pub chids: Option>, - // Time in seconds from epoch - #[serde(skip_serializing_if = "Option::is_none")] - timestamp: Option, - // DynamoDB expiration timestamp per - // https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html - expiry: u32, - // TTL value provided by application server for the message - #[serde(skip_serializing_if = "Option::is_none")] - ttl: Option, - #[serde(skip_serializing_if = "Option::is_none")] - data: Option, - #[serde(skip_serializing_if = "Option::is_none")] - headers: Option, - // This is the acknowledgement-id used for clients to ack that they have received the - // message. Some Python code refers to this as a message_id. Endpoints generate this - // value before sending it to storage or a connection node. - #[serde(skip_serializing_if = "Option::is_none")] - updateid: Option, -} - -impl DynamoDbNotification { - fn parse_sort_key(key: &str) -> Result { - lazy_static! { - static ref RE: RegexSet = - RegexSet::new(&[r"^01:\S+:\S+$", r"^02:\d+:\S+$", r"^\S{3,}:\S+$",]).unwrap(); - } - if !RE.is_match(key) { - return Err("Invalid chidmessageid".into()); - } - - let v: Vec<&str> = key.split(':').collect(); - match v[0] { - "01" => { - if v.len() != 3 { - return Err("Invalid topic key".into()); - } - let (channel_id, topic) = (v[1], v[2]); - let channel_id = Uuid::parse_str(channel_id)?; - Ok(RangeKey { - channel_id, - topic: Some(topic.to_string()), - sortkey_timestamp: None, - legacy_version: None, - }) - } - "02" => { - if v.len() != 3 { - return Err("Invalid topic key".into()); - } - let (sortkey, channel_id) = (v[1], v[2]); - let channel_id = Uuid::parse_str(channel_id)?; - Ok(RangeKey { - channel_id, - topic: None, - sortkey_timestamp: Some(sortkey.parse()?), - legacy_version: None, - }) - } - _ => { - if v.len() != 2 { - return Err("Invalid topic key".into()); - } - let (channel_id, legacy_version) = (v[0], v[1]); - let channel_id = Uuid::parse_str(channel_id)?; - Ok(RangeKey { - channel_id, - topic: None, - sortkey_timestamp: None, - legacy_version: Some(legacy_version.to_string()), - }) - } - } - } - - // TODO: Implement as TryFrom whenever that lands - pub fn into_notif(self) -> Result { - let key = Self::parse_sort_key(&self.chidmessageid)?; - let version = key - .legacy_version - .or(self.updateid) - .ok_or("No valid updateid/version found")?; - - Ok(Notification { - channel_id: key.channel_id, - version, - ttl: self.ttl.ok_or("No TTL found")?, - timestamp: self.timestamp.ok_or("No timestamp found")?, - topic: key.topic, - data: self.data, - headers: self.headers.map(|m| m.into()), - sortkey_timestamp: key.sortkey_timestamp, - }) - } - - pub fn from_notif(uaid: &Uuid, val: Notification) -> Self { - Self { - uaid: *uaid, - chidmessageid: val.sort_key(), - timestamp: Some(val.timestamp), - expiry: sec_since_epoch() as u32 + min(val.ttl, MAX_EXPIRY as u32), - ttl: Some(val.ttl), - data: val.data, - headers: val.headers.map(|h| h.into()), - updateid: Some(val.version), - ..Default::default() - } - } -} - -struct RangeKey { - channel_id: Uuid, - topic: Option, - pub sortkey_timestamp: Option, - legacy_version: Option, -} - -#[cfg(test)] -mod tests { - use super::DynamoDbNotification; - use util::us_since_epoch; - use uuid::Uuid; - - #[test] - fn test_parse_sort_key_ver1() { - let chid = Uuid::new_v4(); - let chidmessageid = format!("01:{}:mytopic", chid.hyphenated().to_string()); - let key = DynamoDbNotification::parse_sort_key(&chidmessageid).unwrap(); - assert_eq!(key.topic, Some("mytopic".to_string())); - assert_eq!(key.channel_id, chid); - assert_eq!(key.sortkey_timestamp, None); - } - - #[test] - fn test_parse_sort_key_ver2() { - let chid = Uuid::new_v4(); - let sortkey_timestamp = us_since_epoch(); - let chidmessageid = format!("02:{}:{}", sortkey_timestamp, chid.hyphenated().to_string()); - let key = DynamoDbNotification::parse_sort_key(&chidmessageid).unwrap(); - assert_eq!(key.topic, None); - assert_eq!(key.channel_id, chid); - assert_eq!(key.sortkey_timestamp, Some(sortkey_timestamp)); - } - - #[test] - fn test_parse_sort_key_bad_values() { - for val in vec!["02j3i2o", "03:ffas:wef", "01::mytopic", "02:oops:ohnoes"] { - let key = DynamoDbNotification::parse_sort_key(val); - assert!(key.is_err()); - } - } -} diff --git a/autopush_rs/src/db/util.rs b/autopush_rs/src/db/util.rs deleted file mode 100644 index 1b72e14f..00000000 --- a/autopush_rs/src/db/util.rs +++ /dev/null @@ -1,15 +0,0 @@ -use chrono::Utc; -use rand::{thread_rng, Rng}; - -/// Generate a last_connect -/// -/// This intentionally generates a limited set of keys for each month in a -/// known sequence. For each month, there's 24 hours * 10 random numbers for -/// a total of 240 keys per month depending on when the user migrates forward. -pub fn generate_last_connect() -> u64 { - let today = Utc::now(); - let mut rng = thread_rng(); - let num = rng.gen_range(0, 10); - let val = format!("{}{:04}", today.format("%Y%m%H"), num); - val.parse::().unwrap() -} diff --git a/autopush_rs/src/errors.rs b/autopush_rs/src/errors.rs deleted file mode 100644 index fed9943b..00000000 --- a/autopush_rs/src/errors.rs +++ /dev/null @@ -1,81 +0,0 @@ -//! Error handling for Rust -//! -//! This module defines various utilities for handling errors in the Rust -//! thread. This uses the `error-chain` crate to ergonomically define errors, -//! enable them for usage with `?`, and otherwise give us some nice utilities. -//! It's expected that this module is always glob imported: -//! -//! use errors::*; -//! -//! And functions in general should then return `Result<()>`. You can add extra -//! error context via `chain_err`: -//! -//! let e = some_function_returning_a_result().chain_err(|| { -//! "some extra context here to make a nicer error" -//! })?; -//! -//! And you can also use the `MyFuture` type alias for "nice" uses of futures -//! -//! fn add(a: i32) -> MyFuture { -//! // .. -//! } -//! -//! You can find some more documentation about this in the `error-chain` crate -//! online. - -use std::any::Any; -use std::error; -use std::io; -use std::num; - -use cadence; -use config; -use futures::Future; -use httparse; -use sentry; -use serde_json; -use tungstenite; -use uuid; - -error_chain! { - foreign_links { - Ws(tungstenite::Error); - Io(io::Error); - Json(serde_json::Error); - Httparse(httparse::Error); - MetricError(cadence::MetricError); - SentryError(sentry::Error); - UuidParseError(uuid::ParseError); - ParseIntError(num::ParseIntError); - ConfigError(config::ConfigError); - } - - errors { - Thread(payload: Box) { - description("thread panicked") - } - } -} - -pub type MyFuture = Box>; - -pub trait FutureChainErr { - fn chain_err(self, callback: F) -> MyFuture - where - F: FnOnce() -> E + 'static, - E: Into; -} - -impl FutureChainErr for F -where - F: Future + 'static, - F::Error: error::Error + Send + 'static, -{ - fn chain_err(self, callback: C) -> MyFuture - where - C: FnOnce() -> E + 'static, - E: Into, - { - Box::new(self.then(|r| r.chain_err(callback))) - } -} diff --git a/autopush_rs/src/http.rs b/autopush_rs/src/http.rs deleted file mode 100644 index 7ae901f0..00000000 --- a/autopush_rs/src/http.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! Internal router HTTP API -//! -//! Accepts PUT requests to deliver notifications to a connected client or trigger -//! a client to check storage. -//! -//! Valid URL's: -//! PUT /push/UAID - Deliver notification to a client -//! PUT /notify/UAID - Tell a client to check storage - -use std::rc::Rc; -use std::str; - -use futures::future::ok; -use futures::{Future, Stream}; -use hyper; -use hyper::{Method, StatusCode}; -use serde_json; -use tokio_service::Service; -use uuid::Uuid; - -use server::Server; - -pub struct Push(pub Rc); - -impl Service for Push { - type Request = hyper::Request; - type Response = hyper::Response; - type Error = hyper::Error; - type Future = Box>; - - fn call(&self, req: hyper::Request) -> Self::Future { - let mut response = hyper::Response::new(); - let req_path = req.path().to_string(); - let path_vec: Vec<&str> = req_path.split('/').collect(); - if path_vec.len() != 3 { - response.set_status(StatusCode::NotFound); - return Box::new(ok(response)); - } - let (method_name, uaid) = (path_vec[1], path_vec[2]); - let uaid = match Uuid::parse_str(uaid) { - Ok(id) => id, - Err(_) => { - debug!("uri not uuid: {}", req.uri().to_string()); - response.set_status(StatusCode::BadRequest); - return Box::new(ok(response)); - } - }; - let srv = self.0.clone(); - match (req.method(), method_name, uaid) { - (&Method::Put, "push", uaid) => { - // Due to consumption of body as a future we must return here - let body = req.body().concat2(); - return Box::new(body.and_then(move |body| { - let s = String::from_utf8(body.to_vec()).unwrap(); - if let Ok(msg) = serde_json::from_str(&s) { - if srv.notify_client(uaid, msg).is_ok() { - Ok(hyper::Response::new().with_status(StatusCode::Ok)) - } else { - Ok(hyper::Response::new() - .with_status(StatusCode::BadGateway) - .with_body("Client not available.")) - } - } else { - Ok(hyper::Response::new() - .with_status(hyper::StatusCode::BadRequest) - .with_body("Unable to decode body payload")) - } - })); - } - (&Method::Put, "notif", uaid) => { - if srv.check_client_storage(uaid).is_ok() { - response.set_status(StatusCode::Ok) - } else { - response.set_status(StatusCode::BadGateway); - response.set_body("Client not available."); - } - }, - (_, "push", _) | (_, "notif", _) => response.set_status(StatusCode::MethodNotAllowed), - _ => response.set_status(StatusCode::NotFound), - }; - Box::new(ok(response)) - } -} diff --git a/autopush_rs/src/lib.rs b/autopush_rs/src/lib.rs deleted file mode 100644 index c5be8bfb..00000000 --- a/autopush_rs/src/lib.rs +++ /dev/null @@ -1,129 +0,0 @@ -//! WIP: Implementation of Web Push ("autopush" as well) in Rust -//! -//! This crate currently provides an implementation of an asynchronous Web Push -//! server which is intended to be interfaced with from Python. The crate mostly -//! has a C API which is driven from `__init__.py` in Python and orchestrated -//! from Python. This is currently done to help ease the transition from the old -//! Python implementation to the new Rust implementation. Currently there's a -//! good bit of API calls to remote services still implemented in Python, but -//! the thinking is that over time these services will be rewritten in to Rust -//! and the Python codebase will shrink. -//! -//! In any case though, this'll focus mainly on the Rust bits rather than the -//! Python bits! It's worth nothing though that this crate is intended to be -//! used with `cffi` in Python, which is "seamlessly" worked with through the -//! `snaek` Python dependency. That basically just means that Python "headers" -//! for this Rust crate are generated automatically. -//! -//! # High level overview -//! -//! At 10,000 feet the general architecture here is that the main Python thread -//! spins up a Rust thread which actually does all the relevant I/O. The one -//! Rust thread uses a `Core` from `tokio-core` to perform all I/O and schedule -//! asynchronous tasks. The `tungstenite` crate is used to parse and manage the -//! WebSocket protocol, with `tokio_tungstenite` being a nicer wrapper for -//! futures-style APIs. -//! -//! The entire server is written in an asynchronous fashion using the `futures` -//! crate in Rust. This basically just means that everything is exposed as a -//! future (similar to the concept in other languages) and that's how bits and -//! pieces are chained together. -//! -//! Each connected client maintains a state machine of where it is in the -//! webpush protocol (see `states.dot` at the root of this repository). Note -//! that not all states are implemented yet, this is a work in progress! All I/O -//! is managed by Rust and various state transitions are managed by Rust as -//! well. Movement between states happens typically as a result of calls into -//! Python. The various operations here will call into Python to do things like -//! db/HTTP requests and then the results are interpreted in Rust to progress -//! the state machine. -//! -//! # Module index -//! -//! There's a number of modules that currently make up the Rust implementation, -//! and one-line summaries of these are: -//! -//! * `queue` - a MPMC queue which is used to send messages to Python and Python -//! uses to delegate work to worker threads. -//! * `server` - the main bindings for the WebPush server, where the tokio -//! `Core` is created and managed inside of the Rust thread. -//! * `client` - this is where the state machine for each connected client is -//! located, managing connections over time and sending out notifications as -//! they arrive. -//! * `protocol` - a definition of the Web Push protocol messages which are send -//! over websockets. -//! * `call` - definitions of various calls that can be made into Python, each -//! of which returning a future of the response. -//! -//! Other modules tend to be miscellaneous implementation details and likely -//! aren't as relevant to the Web Push implementation. -//! -//! Otherwise be sure to check out each module for more documentation! -extern crate base64; -extern crate bytes; -extern crate cadence; -extern crate chan_signal; -extern crate chrono; -extern crate config; -extern crate docopt; -extern crate fernet; -#[macro_use] -extern crate futures; -extern crate futures_backoff; -extern crate hex; -extern crate httparse; -extern crate hyper; -#[macro_use] -extern crate lazy_static; -extern crate libc; -#[macro_use] -extern crate matches; -extern crate mozsvc_common; -extern crate openssl; -extern crate rand; -extern crate regex; -extern crate reqwest; -extern crate rusoto_core; -extern crate rusoto_credential; -extern crate rusoto_dynamodb; -extern crate sentry; -extern crate serde; -#[macro_use] -extern crate serde_derive; -extern crate serde_dynamodb; -#[macro_use] -extern crate serde_json; -#[macro_use] -extern crate slog; -extern crate slog_async; -extern crate slog_mozlog_json; -#[macro_use] -extern crate slog_scope; -extern crate slog_stdlog; -extern crate slog_term; -#[macro_use] -extern crate state_machine_future; -extern crate time; -extern crate tokio_core; -extern crate tokio_io; -extern crate tokio_openssl; -extern crate tokio_service; -extern crate tokio_tungstenite; -extern crate tungstenite; -extern crate uuid; -extern crate woothee; - -#[macro_use] -extern crate error_chain; - -#[macro_use] -mod db; -mod client; -pub mod errors; -mod http; -mod logging; -mod protocol; -pub mod server; -pub mod settings; -#[macro_use] -mod util; diff --git a/autopush_rs/src/logging.rs b/autopush_rs/src/logging.rs deleted file mode 100644 index d4eb24b5..00000000 --- a/autopush_rs/src/logging.rs +++ /dev/null @@ -1,50 +0,0 @@ -use std::io; - -use errors::Result; - -use mozsvc_common::{aws::get_ec2_instance_id, get_hostname}; -use slog::{self, Drain}; -use slog_async; -use slog_mozlog_json::MozLogJson; -use slog_scope; -use slog_stdlog; -use slog_term; - -pub fn init_logging(json: bool) -> Result<()> { - let logger = if json { - let hostname = get_ec2_instance_id() - .map(&str::to_owned) - .or_else(get_hostname) - .ok_or_else(|| "Couldn't get_hostname")?; - - let drain = MozLogJson::new(io::stdout()) - .logger_name(format!( - "{}-{}", - env!("CARGO_PKG_NAME"), - env!("CARGO_PKG_VERSION") - )) - .msg_type(format!("{}:log", env!("CARGO_PKG_NAME"))) - .hostname(hostname) - .build() - .fuse(); - let drain = slog_async::Async::new(drain).build().fuse(); - slog::Logger::root(drain, slog_o!()) - } else { - let decorator = slog_term::TermDecorator::new().build(); - let drain = slog_term::FullFormat::new(decorator).build().fuse(); - let drain = slog_async::Async::new(drain).build().fuse(); - slog::Logger::root(drain, slog_o!()) - }; - // XXX: cancel slog_scope's NoGlobalLoggerSet for now, it's difficult to - // prevent it from potentially panicing during tests. reset_logging resets - // the global logger during shutdown anyway: - // https://github.com/slog-rs/slog/issues/169 - slog_scope::set_global_logger(logger).cancel_reset(); - slog_stdlog::init().ok(); - Ok(()) -} - -pub fn reset_logging() { - let logger = slog::Logger::root(slog::Discard, o!()); - slog_scope::set_global_logger(logger).cancel_reset(); -} diff --git a/autopush_rs/src/main.rs b/autopush_rs/src/main.rs deleted file mode 100644 index 8738c04f..00000000 --- a/autopush_rs/src/main.rs +++ /dev/null @@ -1,53 +0,0 @@ -#[macro_use] -extern crate serde_derive; -extern crate autopush; -extern crate chan_signal; -extern crate docopt; - -use std::env; - -use chan_signal::Signal; -use docopt::Docopt; - -use autopush::errors::{Result, ResultExt}; -use autopush::server::{AutopushServer, ServerOptions}; -use autopush::settings::Settings; - -const USAGE: &'static str = " -Usage: autopush_rs [options] - -Options: - -h, --help Show this message. - --config-connection=CONFIGFILE Connection configuration file path. - --config-shared=CONFIGFILE Common configuration file path. -"; - -#[derive(Debug, Deserialize)] -struct Args { - flag_config_connection: Option, - flag_config_shared: Option, -} - -fn main() -> Result<()> { - let signal = chan_signal::notify(&[Signal::INT, Signal::TERM]); - let args: Args = Docopt::new(USAGE) - .and_then(|d| d.deserialize()) - .unwrap_or_else(|e| e.exit()); - let mut filenames = Vec::new(); - if let Some(shared_filename) = args.flag_config_shared { - filenames.push(shared_filename); - } - if let Some(config_filename) = args.flag_config_connection { - filenames.push(config_filename); - } - let settings = Settings::with_env_and_config_files(&filenames)?; - // Setup the AWS env var if it was set - if let Some(ref ddb_local) = settings.aws_ddb_endpoint { - env::set_var("AWS_LOCAL_DYNAMODB", ddb_local); - } - let server_opts = ServerOptions::from_settings(settings)?; - let server = AutopushServer::new(server_opts); - server.start(); - signal.recv().unwrap(); - server.stop().chain_err(|| "Failed to shutdown properly") -} diff --git a/autopush_rs/src/protocol.rs b/autopush_rs/src/protocol.rs deleted file mode 100644 index e318c6ae..00000000 --- a/autopush_rs/src/protocol.rs +++ /dev/null @@ -1,158 +0,0 @@ -//! Definition of Internal Router, Python, and Websocket protocol messages -//! -//! This module is a structured definition of several protocol. Both -//! messages received from the client and messages sent from the server are -//! defined here. The `derive(Deserialize)` and `derive(Serialize)` annotations -//! are used to generate the ability to serialize these structures to JSON, -//! using the `serde` crate. More docs for serde can be found at -//! https://serde.rs - -use std::collections::HashMap; -use uuid::Uuid; - -use util::ms_since_epoch; - -// Used for the server to flag a webpush client to deliver a Notification or Check storage -pub enum ServerNotification { - CheckStorage, - Notification(Notification), - Disconnect, -} - -impl Default for ServerNotification { - fn default() -> Self { - ServerNotification::Disconnect - } -} - -#[derive(Deserialize)] -#[serde(tag = "messageType", rename_all = "snake_case")] -pub enum ClientMessage { - Hello { - uaid: Option, - #[serde(rename = "channelIDs", skip_serializing_if = "Option::is_none")] - channel_ids: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - use_webpush: Option, - #[serde(skip_serializing_if = "Option::is_none")] - broadcasts: Option>, - }, - - Register { - #[serde(rename = "channelID")] - channel_id: String, - key: Option, - }, - - Unregister { - #[serde(rename = "channelID")] - channel_id: Uuid, - code: Option, - }, - - BroadcastSubscribe { - broadcasts: HashMap, - }, - - Ack { - updates: Vec, - }, - - Nack { - code: Option, - version: String, - }, -} - -#[derive(Deserialize)] -pub struct ClientAck { - #[serde(rename = "channelID")] - pub channel_id: Uuid, - pub version: String, -} - -#[derive(Serialize)] -#[serde(tag = "messageType", rename_all = "snake_case")] -pub enum ServerMessage { - Hello { - uaid: String, - status: u32, - #[serde(skip_serializing_if = "Option::is_none")] - use_webpush: Option, - broadcasts: HashMap, - }, - - Register { - #[serde(rename = "channelID")] - channel_id: Uuid, - status: u32, - #[serde(rename = "pushEndpoint")] - push_endpoint: String, - }, - - Unregister { - #[serde(rename = "channelID")] - channel_id: Uuid, - status: u32, - }, - - Broadcast { - broadcasts: HashMap, - }, - - Notification(Notification), -} - -#[derive(Serialize, Default, Deserialize, Clone, Debug)] -pub struct Notification { - #[serde(rename = "channelID")] - pub channel_id: Uuid, - pub version: String, - #[serde(default = "default_ttl", skip_serializing)] - pub ttl: u32, - #[serde(skip_serializing_if = "Option::is_none")] - pub topic: Option, - pub timestamp: u32, - #[serde(skip_serializing_if = "Option::is_none")] - pub data: Option, - #[serde(skip_serializing)] - pub sortkey_timestamp: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub headers: Option>, -} - -impl Notification { - /// Return an appropriate sort_key to use for the chidmessageid - /// - /// For new messages: - /// 02:{sortkey_timestamp}:{chid} - /// - /// For topic messages: - /// 01:{chid}:{topic} - /// - /// Old format for non-topic messages that is no longer returned: - /// {chid}:{message_id} - pub fn sort_key(&self) -> String { - let chid = self.channel_id.hyphenated(); - if let Some(ref topic) = self.topic { - format!("01:{}:{}", chid, topic) - } else if let Some(sortkey_timestamp) = self.sortkey_timestamp { - format!( - "02:{}:{}", - if sortkey_timestamp == 0 { - ms_since_epoch() - } else { - sortkey_timestamp - }, - chid - ) - } else { - // Legacy messages which we should never get anymore - format!("{}:{}", chid, self.version) - } - } -} - -fn default_ttl() -> u32 { - 0 -} diff --git a/autopush_rs/src/server/dispatch.rs b/autopush_rs/src/server/dispatch.rs deleted file mode 100644 index 25091e3f..00000000 --- a/autopush_rs/src/server/dispatch.rs +++ /dev/null @@ -1,92 +0,0 @@ -//! A future to figure out where we're going to dispatch a TCP socket. -//! -//! When the websocket server receives a TCP connection it may be a websocket -//! request or a general HTTP request. Right now the websocket library we're -//! using, Tungstenite, doesn't have built-in support for handling this -//! situation, so we roll our own. -//! -//! The general idea here is that we're going to read just enough data off the -//! socket to parse an initial HTTP request. This request will be parsed by the -//! `httparse` crate. Once we've got a request we take a look at the headers and -//! if we find a websocket upgrade we classify it as a websocket request. If -//! it's otherwise a `/status` request, we return that we're supposed to get the -//! status, and finally after all that if it doesn't match we return an error. -//! -//! This is basically a "poor man's" HTTP router and while it should be good -//! enough for now it should probably be extended/refactored in the future! -//! -//! Note that also to implement this we buffer the request that we read in -//! memory and then attach that to a socket once we've classified what kind of -//! socket this is. That's done to replay the bytes we read again for the -//! tungstenite library, which'll duplicate header parsing but we don't have -//! many other options for now! - -use bytes::BytesMut; -use futures::{Future, Poll}; -use httparse; -use tokio_core::net::TcpStream; -use tokio_io::AsyncRead; - -use errors::*; -use server::tls::MaybeTlsStream; -use server::webpush_io::WebpushIo; - -pub struct Dispatch { - socket: Option>, - data: BytesMut, -} - -pub enum RequestType { - Websocket, - Status, - LogCheck, -} - -impl Dispatch { - pub fn new(socket: MaybeTlsStream) -> Self { - Self { - socket: Some(socket), - data: BytesMut::new(), - } - } -} - -impl Future for Dispatch { - type Item = (WebpushIo, RequestType); - type Error = Error; - - fn poll(&mut self) -> Poll<(WebpushIo, RequestType), Error> { - loop { - if self.data.len() == self.data.capacity() { - self.data.reserve(16); // get some extra space - } - if try_ready!(self.socket.as_mut().unwrap().read_buf(&mut self.data)) == 0 { - return Err("early eof".into()); - } - let ty = { - let mut headers = [httparse::EMPTY_HEADER; 16]; - let mut req = httparse::Request::new(&mut headers); - match req.parse(&self.data)? { - httparse::Status::Complete(_) => {} - httparse::Status::Partial => continue, - } - - if req.headers.iter().any(|h| h.name == "Upgrade") { - RequestType::Websocket - } else { - match req.path { - Some(ref path) if path.starts_with("/status") => RequestType::Status, - Some(ref path) if path.starts_with("/v1/err/crit") => RequestType::LogCheck, - _ => { - debug!("unknown http request {:?}", req); - return Err("unknown http request".into()); - } - } - } - }; - - let tcp = self.socket.take().unwrap(); - return Ok((WebpushIo::new(tcp, self.data.take()), ty).into()); - } - } -} diff --git a/autopush_rs/src/server/metrics.rs b/autopush_rs/src/server/metrics.rs deleted file mode 100644 index 3654ff32..00000000 --- a/autopush_rs/src/server/metrics.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! Metrics tie-ins - -use std::net::UdpSocket; - -use cadence::{BufferedUdpMetricSink, NopMetricSink, QueuingMetricSink, StatsdClient}; - -use errors::*; -use server::ServerOptions; - -/// Create a cadence StatsdClient from the given options -pub fn metrics_from_opts(opts: &ServerOptions) -> Result { - Ok(if let Some(statsd_host) = opts.statsd_host.as_ref() { - let socket = UdpSocket::bind("0.0.0.0:0")?; - socket.set_nonblocking(true)?; - - let host = (statsd_host.as_str(), opts.statsd_port); - let udp_sink = BufferedUdpMetricSink::from(host, socket)?; - let sink = QueuingMetricSink::from(udp_sink); - StatsdClient::from_sink("autopush", sink) - } else { - StatsdClient::from_sink("autopush", NopMetricSink) - }) -} diff --git a/autopush_rs/src/server/mod.rs b/autopush_rs/src/server/mod.rs deleted file mode 100644 index 10d5bd45..00000000 --- a/autopush_rs/src/server/mod.rs +++ /dev/null @@ -1,988 +0,0 @@ -use std::cell::{Cell, RefCell}; -use std::collections::HashMap; -use std::default::Default; -use std::env; -use std::io; -use std::net::SocketAddr; -use std::panic; -use std::panic::PanicInfo; -use std::path::PathBuf; -use std::rc::Rc; -use std::sync::Arc; -use std::thread; -use std::time::{Duration, Instant}; - -use base64; -use cadence::StatsdClient; -use fernet::{Fernet, MultiFernet}; -use futures::sync::oneshot; -use futures::task; -use futures::{Async, AsyncSink, Future, Poll, Sink, StartSend, Stream}; -use hex; -use hyper::server::Http; -use hyper::{self, header, StatusCode}; -use openssl::hash; -use openssl::ssl::SslAcceptor; -use reqwest; -use sentry; -use serde_json; -use time; -use tokio_core::net::TcpListener; -use tokio_core::reactor::{Core, Handle, Timeout}; -use tokio_io; -use tokio_tungstenite::{accept_hdr_async, WebSocketStream}; -use tungstenite::handshake::server::Request; -use tungstenite::Message; -use uuid::Uuid; - -use client::{Client, RegisteredClient}; -use db::DynamoStorage; -use errors::*; -use errors::{Error, Result}; -use http; -use logging; -use protocol::{ClientMessage, Notification, ServerMessage, ServerNotification}; -use server::dispatch::{Dispatch, RequestType}; -use server::metrics::metrics_from_opts; -use server::webpush_io::WebpushIo; -use settings::Settings; -use util::megaphone::{ - ClientServices, MegaphoneAPIResponse, Service, ServiceChangeTracker, ServiceClientInit, -}; -use util::{timeout, RcObject}; - -mod dispatch; -mod metrics; -mod tls; -mod webpush_io; - -const UAHEADER: &str = "User-Agent"; - -fn ito_dur(seconds: u32) -> Option { - if seconds == 0 { - None - } else { - Some(Duration::new(seconds.into(), 0)) - } -} - -fn fto_dur(seconds: f64) -> Option { - if seconds == 0.0 { - None - } else { - Some(Duration::new( - seconds as u64, - (seconds.fract() * 1_000_000_000.0) as u32, - )) - } -} - -// a signaler to shut down a tokio Core and its associated thread -struct ShutdownHandle(oneshot::Sender<()>, thread::JoinHandle<()>); - -pub struct AutopushServer { - opts: Arc, - shutdown_handles: Cell>>, -} - -impl AutopushServer { - pub fn new(opts: ServerOptions) -> Self { - Self { - opts: Arc::new(opts), - shutdown_handles: Cell::new(None), - } - } - - pub fn start(&self) { - logging::init_logging(!self.opts.human_logs).expect("init_logging failed"); - let handles = Server::start(&self.opts).expect("failed to start server"); - self.shutdown_handles.set(Some(handles)); - } - - /// Blocks execution of the calling thread until the helper thread with the - /// tokio reactor has exited. - pub fn stop(&self) -> Result<()> { - let mut result = Ok(()); - if let Some(shutdown_handles) = self.shutdown_handles.take() { - for ShutdownHandle(tx, thread) in shutdown_handles { - let _ = tx.send(()); - if let Err(err) = thread.join() { - result = Err(From::from(ErrorKind::Thread(err))); - } - } - } - logging::reset_logging(); - result - } -} - -pub struct ServerOptions { - pub debug: bool, - pub router_port: u16, - pub port: u16, - fernet: MultiFernet, - pub ssl_key: Option, - pub ssl_cert: Option, - pub ssl_dh_param: Option, - pub open_handshake_timeout: Option, - pub auto_ping_interval: Duration, - pub auto_ping_timeout: Duration, - pub max_connections: Option, - pub close_handshake_timeout: Option, - pub message_table_names: Vec, - pub current_message_month: String, - pub router_table_name: String, - pub router_url: String, - pub endpoint_url: String, - pub statsd_host: Option, - pub statsd_port: u16, - pub megaphone_api_url: Option, - pub megaphone_api_token: Option, - pub megaphone_poll_interval: Duration, - pub human_logs: bool, -} - -impl ServerOptions { - pub fn from_settings(settings: Settings) -> Result { - let fernets: Vec = settings - .crypto_key - .split(',') - .map(|s| s.trim().to_string()) - .map(|key| Fernet::new(&key).expect("Invalid key supplied")) - .collect(); - let fernet = MultiFernet::new(fernets); - let ddb = DynamoStorage::new(); - let message_table_names = ddb - .list_message_tables(&settings.message_tablename) - .expect("Failed to locate message tables"); - let router_url = settings.router_url(); - let endpoint_url = settings.endpoint_url(); - let mut opts = Self { - debug: settings.debug, - port: settings.port, - fernet, - router_port: settings.router_port, - statsd_host: if settings.statsd_host.is_empty() { - None - } else { - Some(settings.statsd_host) - }, - statsd_port: settings.statsd_port, - message_table_names, - current_message_month: "".to_string(), - router_table_name: settings.router_tablename, - router_url, - endpoint_url, - ssl_key: settings.router_ssl_key.map(PathBuf::from), - ssl_cert: settings.router_ssl_cert.map(PathBuf::from), - ssl_dh_param: settings.router_ssl_dh_param.map(PathBuf::from), - auto_ping_interval: fto_dur(settings.auto_ping_interval) - .expect("auto ping interval cannot be 0"), - auto_ping_timeout: fto_dur(settings.auto_ping_timeout) - .expect("auto ping timeout cannot be 0"), - close_handshake_timeout: ito_dur(settings.close_handshake_timeout), - max_connections: if settings.max_connections == 0 { - None - } else { - Some(settings.max_connections) - }, - open_handshake_timeout: ito_dur(5), - megaphone_api_url: settings.megaphone_api_url, - megaphone_api_token: settings.megaphone_api_token, - megaphone_poll_interval: ito_dur(settings.megaphone_poll_interval) - .expect("megaphone poll interval cannot be 0"), - human_logs: settings.human_logs, - }; - opts.message_table_names.sort_unstable(); - opts.current_message_month = opts - .message_table_names - .last() - .expect("No last message month found") - .to_string(); - Ok(opts) - } -} - -pub struct Server { - uaids: RefCell>, - broadcaster: RefCell, - pub ddb: DynamoStorage, - open_connections: Cell, - tls_acceptor: Option, - pub opts: Arc, - pub handle: Handle, - pub metrics: StatsdClient, -} - -impl Server { - /// Creates a new server handle to send to python. - /// - /// This will spawn a new server with the `opts` specified, spinning up a - /// separate thread for the tokio reactor. The returned ShutdownHandles can - /// be used to interact with it (e.g. shut it down). - fn start(opts: &Arc) -> Result> { - let mut shutdown_handles = vec![]; - if let Some(handle) = Server::start_sentry()? { - shutdown_handles.push(handle); - } - - let (inittx, initrx) = oneshot::channel(); - let (donetx, donerx) = oneshot::channel(); - - let opts = opts.clone(); - let thread = thread::spawn(move || { - let (srv, mut core) = match Server::new(&opts) { - Ok(core) => { - inittx.send(None).unwrap(); - core - } - Err(e) => return inittx.send(Some(e)).unwrap(), - }; - - // Internal HTTP server setup - { - let handle = core.handle(); - let addr = SocketAddr::from(([0, 0, 0, 0], srv.opts.router_port)); - let push_listener = TcpListener::bind(&addr, &handle).unwrap(); - let http = Http::::new(); - let push_srv = push_listener.incoming().for_each(move |(socket, _)| { - handle.spawn( - http.serve_connection(socket, http::Push(srv.clone())) - .map(|_| ()) - .map_err(|e| debug!("Http server connection error: {}", e)), - ); - Ok(()) - }); - core.handle().spawn(push_srv.then(|res| { - debug!("Http server {:?}", res); - Ok(()) - })); - } - - core.run(donerx).expect("Main Core run error"); - }); - - match initrx.wait() { - Ok(Some(e)) => Err(e), - Ok(None) => { - shutdown_handles.push(ShutdownHandle(donetx, thread)); - Ok(shutdown_handles) - } - Err(_) => panic::resume_unwind(thread.join().unwrap_err()), - } - } - - /// Setup Sentry logging if a SENTRY_DSN exists - fn start_sentry() -> Result> { - let creds = match env::var("SENTRY_DSN") { - Ok(dsn) => dsn.parse::()?, - Err(_) => return Ok(None), - }; - - // Spin up a new thread with a new reactor core for the sentry handler - let (donetx, donerx) = oneshot::channel(); - let thread = thread::spawn(move || { - let mut core = Core::new().expect("Unable to create core"); - let sentry = sentry::Sentry::from_settings(core.handle(), Default::default(), creds); - // Get the prior panic hook - let hook = panic::take_hook(); - sentry.register_panic_handler(Some(move |info: &PanicInfo| -> () { - hook(info); - })); - core.run(donerx).expect("Sentry Core run error"); - }); - - Ok(Some(ShutdownHandle(donetx, thread))) - } - - fn new(opts: &Arc) -> Result<(Rc, Core)> { - let core = Core::new()?; - let broadcaster = if let Some(ref megaphone_url) = opts.megaphone_api_url { - let megaphone_token = opts - .megaphone_api_token - .as_ref() - .expect("Megaphone API requires a Megaphone API Token to be set"); - ServiceChangeTracker::with_api_services(megaphone_url, megaphone_token) - .expect("Unable to initialize megaphone with provided URL") - } else { - ServiceChangeTracker::new(Vec::new()) - }; - let srv = Rc::new(Server { - opts: opts.clone(), - broadcaster: RefCell::new(broadcaster), - ddb: DynamoStorage::new(), - uaids: RefCell::new(HashMap::new()), - open_connections: Cell::new(0), - handle: core.handle(), - tls_acceptor: tls::configure(opts), - metrics: metrics_from_opts(opts)?, - }); - let addr = SocketAddr::from(([0, 0, 0, 0], srv.opts.port)); - let ws_listener = TcpListener::bind(&addr, &srv.handle)?; - - let handle = core.handle(); - let srv2 = srv.clone(); - let ws_srv = ws_listener - .incoming() - .map_err(Error::from) - .for_each(move |(socket, addr)| { - // Make sure we're not handling too many clients before we start the - // websocket handshake. - let max = srv.opts.max_connections.unwrap_or(u32::max_value()); - if srv.open_connections.get() >= max { - info!( - "dropping {} as we already have too many open \ - connections", - addr - ); - return Ok(()); - } - srv.open_connections.set(srv.open_connections.get() + 1); - - // TODO: TCP socket options here? - - // Process TLS (if configured) - let socket = tls::accept(&srv, socket); - - // Figure out if this is a websocket or a `/status` request, - let request = socket.and_then(Dispatch::new); - - // Time out both the TLS accept (if any) along with the dispatch - // to figure out where we're going. - let request = timeout(request, srv.opts.open_handshake_timeout, &handle); - let srv2 = srv.clone(); - let handle2 = handle.clone(); - - let host = format!("{}", addr.ip()); - - // Setup oneshot to extract the user-agent from the header callback - let (uatx, uarx) = oneshot::channel(); - let callback = |req: &Request| { - if let Some(value) = req.headers.find_first(UAHEADER) { - let mut valstr = String::new(); - for c in value.iter() { - let c = *c as char; - valstr.push(c); - } - debug!("Found user-agent string"; "user-agent" => valstr.as_str()); - uatx.send(valstr).unwrap(); - } - debug!("No agent string found"); - Ok(None) - }; - - let client = request.and_then(move |(socket, request)| -> MyFuture<_> { - match request { - RequestType::Status => write_status(socket), - RequestType::LogCheck => write_log_check(socket), - RequestType::Websocket => { - // Perform the websocket handshake on each - // connection, but don't let it take too long. - let ws = accept_hdr_async(socket, callback) - .chain_err(|| "failed to accept client"); - let ws = timeout(ws, srv2.opts.open_handshake_timeout, &handle2); - - // Once the handshake is done we'll start the main - // communication with the client, managing pings - // here and deferring to `Client` to start driving - // the internal state machine. - Box::new( - ws.and_then(move |ws| { - PingManager::new(&srv2, ws, uarx, host) - .chain_err(|| "failed to make ping handler") - }).flatten(), - ) - } - } - }); - - let srv = srv.clone(); - handle.spawn(client.then(move |res| { - srv.open_connections.set(srv.open_connections.get() - 1); - if let Err(e) = res { - let mut error = e.to_string(); - for err in e.iter().skip(1) { - error.push_str("\n"); - error.push_str(&err.to_string()); - } - debug!("{}: {}", addr, error); - } - Ok(()) - })); - - Ok(()) - }); - - if let Some(ref megaphone_url) = opts.megaphone_api_url { - let megaphone_token = opts - .megaphone_api_token - .as_ref() - .expect("Megaphone API requires a Megaphone API Token to be set"); - let fut = MegaphoneUpdater::new( - megaphone_url, - megaphone_token, - opts.megaphone_poll_interval, - &srv2, - ).expect("Unable to start megaphone updater"); - core.handle().spawn(fut.then(|res| { - debug!("megaphone result: {:?}", res.map(drop)); - Ok(()) - })); - } - core.handle().spawn(ws_srv.then(|res| { - debug!("srv res: {:?}", res.map(drop)); - Ok(()) - })); - - Ok((srv2, core)) - } - - /// Create an v1 or v2 WebPush endpoint from the identifiers - /// - /// Both endpoints use bytes instead of hex to reduce ID length. - // v1 is the uaid + chid - // v2 is the uaid + chid + sha256(key).bytes - pub fn make_endpoint(&self, uaid: &Uuid, chid: &Uuid, key: Option) -> Result { - let root = format!("{}/wpush/", self.opts.endpoint_url); - let mut base = hex::decode(uaid.simple().to_string()).chain_err(|| "Error decoding")?; - base.extend(hex::decode(chid.simple().to_string()).chain_err(|| "Error decoding")?); - if let Some(k) = key { - let raw_key = base64::decode_config(&k, base64::URL_SAFE) - .chain_err(|| "Error encrypting payload")?; - let key_digest = hash::hash(hash::MessageDigest::sha256(), &raw_key) - .chain_err(|| "Error creating message digest for key")?; - base.extend(key_digest.iter()); - let encrypted = self - .opts - .fernet - .encrypt(&base) - .trim_matches('=') - .to_string(); - Ok(format!("{}v2/{}", root, encrypted)) - } else { - let encrypted = self - .opts - .fernet - .encrypt(&base) - .trim_matches('=') - .to_string(); - Ok(format!("{}v1/{}", root, encrypted)) - } - } - - /// Informs this server that a new `client` has connected - /// - /// For now just registers internal state by keeping track of the `client`, - /// namely its channel to send notifications back. - pub fn connect_client(&self, client: RegisteredClient) { - debug!("Connecting a client!"); - if let Some(client) = self.uaids.borrow_mut().insert(client.uaid, client) { - // Drop existing connection - let result = client.tx.unbounded_send(ServerNotification::Disconnect); - if result.is_ok() { - debug!("Told client to disconnect as a new one wants to connect"); - } - } - } - - /// A notification has come for the uaid - pub fn notify_client(&self, uaid: Uuid, notif: Notification) -> Result<()> { - let uaids = self.uaids.borrow(); - if let Some(client) = uaids.get(&uaid) { - debug!("Found a client to deliver a notification to"); - let result = client - .tx - .unbounded_send(ServerNotification::Notification(notif)); - if result.is_ok() { - debug!("Dropped notification in queue"); - return Ok(()); - } - } - Err("User not connected".into()) - } - - /// A check for notification command has come for the uaid - pub fn check_client_storage(&self, uaid: Uuid) -> Result<()> { - let uaids = self.uaids.borrow(); - if let Some(client) = uaids.get(&uaid) { - let result = client.tx.unbounded_send(ServerNotification::CheckStorage); - if result.is_ok() { - debug!("Told client to check storage"); - return Ok(()); - } - } - Err("User not connected".into()) - } - - /// The client specified by `uaid` has disconnected. - pub fn disconnet_client(&self, uaid: &Uuid, uid: &Uuid) { - debug!("Disconnecting client!"); - let mut uaids = self.uaids.borrow_mut(); - let client_exists = uaids.get(uaid).map_or(false, |client| client.uid == *uid); - if client_exists { - uaids.remove(uaid).expect("Couldn't remove client?"); - } - } - - /// Generate a new service client list for a newly connected client - pub fn broadcast_init(&self, services: &[Service]) -> ServiceClientInit { - debug!("Initialized broadcast services"); - self.broadcaster.borrow().service_delta(services) - } - - /// Calculate whether there's new service versions to go out - pub fn broadcast_delta(&self, client_services: &mut ClientServices) -> Option> { - self.broadcaster - .borrow() - .change_count_delta(client_services) - } - - /// Add services to be tracked by a client - pub fn client_service_add_service( - &self, - client_services: &mut ClientServices, - services: &[Service], - ) -> Option> { - self.broadcaster - .borrow() - .client_service_add_service(client_services, services) - } -} - -enum MegaphoneState { - Waiting, - Requesting(MyFuture), -} - -struct MegaphoneUpdater { - srv: Rc, - api_url: String, - api_token: String, - state: MegaphoneState, - timeout: Timeout, - poll_interval: Duration, - client: reqwest::unstable::async::Client, -} - -impl MegaphoneUpdater { - fn new( - uri: &str, - token: &str, - poll_interval: Duration, - srv: &Rc, - ) -> io::Result { - let client = reqwest::unstable::async::Client::builder() - .timeout(Duration::from_secs(1)) - .build(&srv.handle) - .expect("Unable to build reqwest client"); - Ok(MegaphoneUpdater { - srv: srv.clone(), - api_url: uri.to_string(), - api_token: token.to_string(), - state: MegaphoneState::Waiting, - timeout: Timeout::new(poll_interval, &srv.handle)?, - poll_interval, - client, - }) - } -} - -impl Future for MegaphoneUpdater { - type Item = (); - type Error = Error; - - fn poll(&mut self) -> Poll<(), Error> { - loop { - let new_state = match self.state { - MegaphoneState::Waiting => { - try_ready!(self.timeout.poll()); - debug!("Sending megaphone API request"); - let fut = self - .client - .get(&self.api_url) - .header(header::Authorization(self.api_token.clone())) - .send() - .and_then(|response| response.error_for_status()) - .and_then(|mut response| response.json()) - .map_err(|_| "Unable to query/decode the API query".into()); - MegaphoneState::Requesting(Box::new(fut)) - } - MegaphoneState::Requesting(ref mut response) => { - let at = Instant::now() + self.poll_interval; - match response.poll() { - Ok(Async::Ready(MegaphoneAPIResponse { broadcasts })) => { - debug!("Fetched broadcasts: {:?}", broadcasts); - let mut broadcaster = self.srv.broadcaster.borrow_mut(); - for srv in Service::from_hashmap(broadcasts) { - broadcaster.add_service(srv); - } - } - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(_) => { - // TODO: Flag sentry that we can't poll megaphone API - debug!("Failed to get response, queue again"); - } - }; - self.timeout.reset(at); - MegaphoneState::Waiting - } - }; - self.state = new_state; - } - } -} - -enum WaitingFor { - SendPing, - Pong, - Close, -} - -enum CloseState { - Exchange(T), - Closing, -} - -struct PingManager { - socket: RcObject>>, - timeout: Timeout, - waiting: WaitingFor, - srv: Rc, - client: CloseState>>>>, -} - -impl PingManager { - fn new( - srv: &Rc, - socket: WebSocketStream, - uarx: oneshot::Receiver, - host: String, - ) -> io::Result { - // The `socket` is itself a sink and a stream, and we've also got a sink - // (`tx`) and a stream (`rx`) to send messages. Half of our job will be - // doing all this proxying: reading messages from `socket` and sending - // them to `tx` while also reading messages from `rx` and sending them - // on `socket`. - // - // Our other job will be to manage the websocket protocol pings going - // out and coming back. The `opts` provided indicate how often we send - // pings and how long we'll wait for the ping to come back before we - // time it out. - // - // To make these tasks easier we start out by throwing the `socket` into - // an `Rc` object. This'll allow us to share it between the ping/pong - // management and message shuffling. - let socket = RcObject::new(WebpushSocket::new(socket)); - Ok(PingManager { - timeout: Timeout::new(srv.opts.auto_ping_interval, &srv.handle)?, - waiting: WaitingFor::SendPing, - socket: socket.clone(), - client: CloseState::Exchange(Client::new(socket, srv, uarx, host)), - srv: srv.clone(), - }) - } -} - -impl Future for PingManager { - type Item = (); - type Error = Error; - - fn poll(&mut self) -> Poll<(), Error> { - let mut socket = self.socket.borrow_mut(); - loop { - if socket.ping { - // Don't check if we already have a delta to broadcast - if socket.broadcast_delta.is_none() { - // Determine if we can do a broadcast check, we need a connected webpush client - if let CloseState::Exchange(ref mut client) = self.client { - if let Some(delta) = client.broadcast_delta() { - socket.broadcast_delta = Some(delta); - } - } - } - - if socket.send_ping()?.is_ready() { - // If we just sent a broadcast, reset the ping interval and clear the delta - if socket.broadcast_delta.is_some() { - let at = Instant::now() + self.srv.opts.auto_ping_interval; - self.timeout.reset(at); - socket.broadcast_delta = None; - self.waiting = WaitingFor::SendPing - } else { - let at = Instant::now() + self.srv.opts.auto_ping_timeout; - self.timeout.reset(at); - self.waiting = WaitingFor::Pong - } - } else { - break; - } - } - debug_assert!(!socket.ping); - match self.waiting { - WaitingFor::SendPing => { - debug_assert!(!socket.pong_timeout); - debug_assert!(!socket.pong_received); - match self.timeout.poll()? { - Async::Ready(()) => { - debug!("scheduling a ping to get sent"); - socket.ping = true; - } - Async::NotReady => break, - } - } - WaitingFor::Pong => { - if socket.pong_received { - // If we received a pong, then switch us back to waiting - // to send out a ping - debug!("pong received, going back to sending a ping"); - debug_assert!(!socket.pong_timeout); - let at = Instant::now() + self.srv.opts.auto_ping_interval; - self.timeout.reset(at); - self.waiting = WaitingFor::SendPing; - socket.pong_received = false; - } else if socket.pong_timeout { - // If our socket is waiting to deliver a pong timeout, - // then no need to keep checking the timer and we can - // keep going - debug!("waiting for socket to see pong timed out"); - break; - } else if self.timeout.poll()?.is_ready() { - // We may not actually be reading messages from the - // websocket right now, could have been waiting on - // something else. Instead of immediately returning an - // error here wait for the stream to return `NotReady` - // when looking for messages, as then we're extra sure - // that no pong was received after this timeout elapsed. - debug!("waited too long for a pong"); - socket.pong_timeout = true; - } else { - break; - } - } - WaitingFor::Close => { - debug_assert!(!socket.pong_timeout); - if self.timeout.poll()?.is_ready() { - if let CloseState::Exchange(ref mut client) = self.client { - client.shutdown(); - } - // So did the shutdown not work? We must call shutdown but no client here? - return Err("close handshake took too long".into()); - } - } - } - } - - // Be sure to always flush out any buffered messages/pings - socket - .poll_complete() - .chain_err(|| "failed routine `poll_complete` call")?; - drop(socket); - - // At this point looks our state of ping management A-OK, so try to - // make progress on our client, and when done with that execute the - // closing handshake. - loop { - match self.client { - CloseState::Exchange(ref mut client) => try_ready!(client.poll()), - CloseState::Closing => return Ok(self.socket.borrow_mut().close()?), - } - - self.client = CloseState::Closing; - if let Some(dur) = self.srv.opts.close_handshake_timeout { - let at = Instant::now() + dur; - self.timeout.reset(at); - self.waiting = WaitingFor::Close; - } - } - } -} - -// Wrapper struct to take a Sink/Stream of `Message` to a Sink/Stream of -// `ClientMessage` and `ServerMessage`. -struct WebpushSocket { - inner: T, - pong_received: bool, - ping: bool, - pong_timeout: bool, - broadcast_delta: Option>, -} - -impl WebpushSocket { - fn new(t: T) -> WebpushSocket { - WebpushSocket { - inner: t, - pong_received: false, - ping: false, - pong_timeout: false, - broadcast_delta: None, - } - } - - fn send_ping(&mut self) -> Poll<(), Error> - where - T: Sink, - Error: From, - { - if self.ping { - let msg = if let Some(broadcasts) = self.broadcast_delta.clone() { - debug!("sending a broadcast delta"); - let server_msg = ServerMessage::Broadcast { - broadcasts: Service::into_hashmap(broadcasts), - }; - let s = serde_json::to_string(&server_msg).chain_err(|| "failed to serialize")?; - Message::Text(s) - } else { - debug!("sending a ping"); - Message::Ping(Vec::new()) - }; - match self.inner.start_send(msg)? { - AsyncSink::Ready => { - debug!("ping sent"); - self.ping = false; - } - AsyncSink::NotReady(_) => { - debug!("ping not ready to be sent"); - return Ok(Async::NotReady); - } - } - } - Ok(Async::Ready(())) - } -} - -impl Stream for WebpushSocket -where - T: Stream, - Error: From, -{ - type Item = ClientMessage; - type Error = Error; - - fn poll(&mut self) -> Poll, Error> { - loop { - let msg = match self.inner.poll()? { - Async::Ready(Some(msg)) => msg, - Async::Ready(None) => return Ok(None.into()), - Async::NotReady => { - // If we don't have any more messages and our pong timeout - // elapsed (set above) then this is where we start - // triggering errors. - if self.pong_timeout { - return Err("failed to receive a pong in time".into()); - } - return Ok(Async::NotReady); - } - }; - match msg { - Message::Text(ref s) => { - trace!("text message {}", s); - let msg = serde_json::from_str(s).chain_err(|| "invalid json text")?; - return Ok(Some(msg).into()); - } - - Message::Binary(_) => return Err("binary messages not accepted".into()), - - // sending a pong is already managed by lower layers, just go to - // the next message - Message::Ping(_) => {} - - // Wake up ourselves to ensure the above ping logic eventually - // sees this pong. - Message::Pong(_) => { - self.pong_received = true; - self.pong_timeout = false; - task::current().notify(); - } - } - } - } -} - -impl Sink for WebpushSocket -where - T: Sink, - Error: From, -{ - type SinkItem = ServerMessage; - type SinkError = Error; - - fn start_send(&mut self, msg: ServerMessage) -> StartSend { - if self.send_ping()?.is_not_ready() { - return Ok(AsyncSink::NotReady(msg)); - } - let s = serde_json::to_string(&msg).chain_err(|| "failed to serialize")?; - match self.inner.start_send(Message::Text(s))? { - AsyncSink::Ready => Ok(AsyncSink::Ready), - AsyncSink::NotReady(_) => Ok(AsyncSink::NotReady(msg)), - } - } - - fn poll_complete(&mut self) -> Poll<(), Error> { - try_ready!(self.send_ping()); - Ok(self.inner.poll_complete()?) - } - - fn close(&mut self) -> Poll<(), Error> { - try_ready!(self.poll_complete()); - Ok(self.inner.close()?) - } -} - -fn write_status(socket: WebpushIo) -> MyFuture<()> { - write_json( - socket, - StatusCode::Ok, - json!({ - "status": "OK", - "version": env!("CARGO_PKG_VERSION"), - }), - ) -} - -fn write_log_check(socket: WebpushIo) -> MyFuture<()> { - let status = StatusCode::ImATeapot; - let code: u16 = status.into(); - - error!("Test Critical Message"; - "status_code" => code, - "errno" => 0, - ); - thread::spawn(|| { - panic!("LogCheck"); - }); - - write_json( - socket, - StatusCode::ImATeapot, - json!({ - "code": code, - "errno": 999, - "error": "Test Failure", - "mesage": "FAILURE:Success", - }), - ) -} - -fn write_json(socket: WebpushIo, status: StatusCode, body: serde_json::Value) -> MyFuture<()> { - let body = body.to_string(); - let data = format!( - "\ - HTTP/1.1 {status}\r\n\ - Server: webpush\r\n\ - Date: {date}\r\n\ - Content-Length: {len}\r\n\ - Content-Type: application/json\r\n\ - \r\n\ - {body}\ - ", - status = status, - date = time::at(time::get_time()).rfc822(), - len = body.len(), - body = body, - ); - Box::new( - tokio_io::io::write_all(socket, data.into_bytes()) - .map(|_| ()) - .chain_err(|| "failed to write status response"), - ) -} diff --git a/autopush_rs/src/server/tls.rs b/autopush_rs/src/server/tls.rs deleted file mode 100644 index 315df39d..00000000 --- a/autopush_rs/src/server/tls.rs +++ /dev/null @@ -1,138 +0,0 @@ -//! TLS support for the autopush server -//! -//! Currently tungstenite in the way we use it just operates generically over an -//! `AsyncRead`/`AsyncWrite` stream, so this provides a `MaybeTlsStream` type -//! which dispatches at runtime whether it's a plaintext or TLS stream after a -//! connection is established. - -use std::fs::File; -use std::io::{self, Read, Write}; -use std::path::Path; -use std::rc::Rc; - -use futures::future; -use futures::{Future, Poll}; -use openssl::dh::Dh; -use openssl::pkey::PKey; -use openssl::ssl::{SslAcceptor, SslMethod, SslMode}; -use openssl::x509::X509; -use tokio_core::net::TcpStream; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_openssl::{SslAcceptorExt, SslStream}; - -use errors::*; -use server::{Server, ServerOptions}; - -/// Creates an `SslAcceptor`, if needed, ready to accept TLS connections. -/// -/// This method is called early on when the server is created and the -/// `SslAcceptor` type is stored globally in the `Server` structure, later used -/// to process all accepted TCP sockets. -pub fn configure(opts: &ServerOptions) -> Option { - let key = match opts.ssl_key { - Some(ref key) => read(key), - None => return None, - }; - let key = PKey::private_key_from_pem(&key).expect("failed to create private key"); - let cert = read(opts.ssl_cert.as_ref().expect("ssl_cert not configured")); - let cert = X509::from_pem(&cert).expect("failed to create certificate"); - - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()) - .expect("failed to create ssl acceptor builder"); - builder - .set_private_key(&key) - .expect("failed to set private key"); - builder - .set_certificate(&cert) - .expect("failed to set certificate"); - builder - .check_private_key() - .expect("private key check failed"); - - if let Some(dh_param) = opts.ssl_dh_param.as_ref() { - let dh_param = Dh::params_from_pem(&read(dh_param)).expect("failed to create dh"); - builder - .set_tmp_dh(&dh_param) - .expect("failed to set dh_param"); - } - - // Should help reduce peak memory consumption for idle connections - builder.set_mode(SslMode::RELEASE_BUFFERS); - - return Some(builder.build()); - - fn read(path: &Path) -> Vec { - let mut out = Vec::new(); - File::open(path) - .expect(&format!("failed to open {:?}", path)) - .read_to_end(&mut out) - .expect(&format!("failed to read {:?}", path)); - out - } -} - -/// Performs the TLS handshake, if necessary, for a socket. -/// -/// This is typically called just after a socket has been accepted from the TCP -/// listener. If the server is configured without TLS then this will immediately -/// return with a plaintext socket, or otherwise it will perform an asynchronous -/// TLS handshake and only resolve once that's completed. -pub fn accept(srv: &Rc, socket: TcpStream) -> MyFuture> { - match srv.tls_acceptor { - Some(ref acceptor) => Box::new( - acceptor - .accept_async(socket) - .map(MaybeTlsStream::Tls) - .chain_err(|| "failed to accept TLS socket"), - ), - None => Box::new(future::ok(MaybeTlsStream::Plain(socket))), - } -} - -pub enum MaybeTlsStream { - Plain(T), - Tls(SslStream), -} - -impl Read for MaybeTlsStream { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - match *self { - MaybeTlsStream::Plain(ref mut s) => s.read(buf), - MaybeTlsStream::Tls(ref mut s) => s.read(buf), - } - } -} - -impl Write for MaybeTlsStream { - fn write(&mut self, buf: &[u8]) -> io::Result { - match *self { - MaybeTlsStream::Plain(ref mut s) => s.write(buf), - MaybeTlsStream::Tls(ref mut s) => s.write(buf), - } - } - - fn flush(&mut self) -> io::Result<()> { - match *self { - MaybeTlsStream::Plain(ref mut s) => s.flush(), - MaybeTlsStream::Tls(ref mut s) => s.flush(), - } - } -} - -impl AsyncRead for MaybeTlsStream { - unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { - match *self { - MaybeTlsStream::Plain(ref s) => s.prepare_uninitialized_buffer(buf), - MaybeTlsStream::Tls(ref s) => s.prepare_uninitialized_buffer(buf), - } - } -} - -impl AsyncWrite for MaybeTlsStream { - fn shutdown(&mut self) -> Poll<(), io::Error> { - match *self { - MaybeTlsStream::Plain(ref mut s) => s.shutdown(), - MaybeTlsStream::Tls(ref mut s) => s.shutdown(), - } - } -} diff --git a/autopush_rs/src/server/webpush_io.rs b/autopush_rs/src/server/webpush_io.rs deleted file mode 100644 index 2dcb06f1..00000000 --- a/autopush_rs/src/server/webpush_io.rs +++ /dev/null @@ -1,66 +0,0 @@ -//! I/O wrapper created through `Dispatch` -//! -//! Most I/O happens through just raw TCP sockets, but at the beginning of a -//! request we'll take a look at the headers and figure out where to route it. -//! After that, for tungstenite the websocket library, we'll want to replay the -//! data we already read as there's no ability to pass this in currently. That -//! means we'll parse headers twice, but alas! - -use std::io::{self, Read, Write}; - -use bytes::BytesMut; -use futures::Poll; -use tokio_core::net::TcpStream; -use tokio_io::{AsyncRead, AsyncWrite}; - -use server::tls::MaybeTlsStream; - -pub struct WebpushIo { - tcp: MaybeTlsStream, - header_to_read: Option, -} - -impl WebpushIo { - pub fn new(tcp: MaybeTlsStream, header: BytesMut) -> Self { - Self { - tcp: tcp, - header_to_read: Some(header), - } - } -} - -impl Read for WebpushIo { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - // Start off by replaying the bytes already read, and after that just - // delegate everything to the internal `TcpStream` - if let Some(ref mut header) = self.header_to_read { - let n = (&header[..]).read(buf)?; - header.split_to(n); - if buf.is_empty() || n > 0 { - return Ok(n); - } - } - self.header_to_read = None; - self.tcp.read(buf) - } -} - -// All `write` calls are routed through the `TcpStream` instance directly as we -// don't buffer this at all. -impl Write for WebpushIo { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.tcp.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.tcp.flush() - } -} - -impl AsyncRead for WebpushIo {} - -impl AsyncWrite for WebpushIo { - fn shutdown(&mut self) -> Poll<(), io::Error> { - AsyncWrite::shutdown(&mut self.tcp) - } -} diff --git a/autopush_rs/src/settings.rs b/autopush_rs/src/settings.rs deleted file mode 100644 index 7af28a93..00000000 --- a/autopush_rs/src/settings.rs +++ /dev/null @@ -1,131 +0,0 @@ -use std::net::ToSocketAddrs; - -use config::{Config, ConfigError, Environment, File}; -use fernet::Fernet; -use mozsvc_common::get_hostname; - -lazy_static! { - static ref HOSTNAME: String = get_hostname().unwrap(); - static ref RESOLVED_HOSTNAME: String = get_resolved_hostname(); -} - -fn get_resolved_hostname() -> String { - let hostname = get_hostname().expect("Can't get hostname"); - hostname - .to_socket_addrs() - .expect("Failed to resolve hostnames") - .last() - .expect("No hostnames found") - .to_string() -} - -#[derive(Debug, Deserialize)] -pub struct Settings { - pub debug: bool, - pub port: u16, - pub hostname: Option, - pub resolve_hostname: bool, - pub router_port: u16, - pub router_hostname: Option, - pub router_tablename: String, - pub message_tablename: String, - pub router_ssl_key: Option, - pub router_ssl_cert: Option, - pub router_ssl_dh_param: Option, - pub auto_ping_interval: f64, - pub auto_ping_timeout: f64, - pub max_connections: u32, - pub close_handshake_timeout: u32, - pub endpoint_scheme: String, - pub endpoint_hostname: Option, - pub endpoint_port: u16, - pub crypto_key: String, - pub statsd_host: String, - pub statsd_port: u16, - pub aws_ddb_endpoint: Option, - pub megaphone_api_url: Option, - pub megaphone_api_token: Option, - pub megaphone_poll_interval: u32, - pub human_logs: bool, -} - -impl Settings { - /// Load the settings from the config files in order first then the environment. - pub fn with_env_and_config_files(filenames: &[String]) -> Result { - let mut s = Config::default(); - // Set our defaults, this can be fixed up drastically later after: - // https://github.com/mehcode/config-rs/issues/60 - s.set_default("debug", false)?; - s.set_default("port", 8080)?; - s.set_default("resolve_hostname", false)?; - s.set_default("router_port", 8081)?; - s.set_default("router_tablename", "router")?; - s.set_default("message_tablename", "message")?; - s.set_default("auto_ping_interval", 300)?; - s.set_default("auto_ping_timeout", 4)?; - s.set_default("max_connections", 0)?; - s.set_default("close_handshake_timeout", 0)?; - s.set_default("endpoint_scheme", "http")?; - s.set_default("endpoint_port", 8082)?; - s.set_default("crypto_key", Fernet::generate_key())?; - s.set_default("statsd_host", "localhost")?; - s.set_default("statsd_port", 8125)?; - s.set_default("megaphone_poll_interval", 30)?; - s.set_default("human_logs", false)?; - - // Merge the configs from the files - for filename in filenames { - s.merge(File::with_name(filename))?; - } - - // Merge the environment overrides - s.merge(Environment::with_prefix("autopush"))?; - s.try_into() - } - - pub fn router_url(&self) -> String { - let router_scheme = if self.router_ssl_key.is_none() { - "http" - } else { - "https" - }; - let hostname = self.host_name(); - format!( - "{}://{}:{}", - router_scheme, - self.router_hostname.as_ref().unwrap_or(&hostname), - self.router_port - ) - } - - pub fn endpoint_url(&self) -> String { - format!( - "{}://{}:{}", - self.endpoint_scheme, - self.endpoint_hostname - .as_ref() - .expect("Endpoint hostname must be supplied"), - self.endpoint_port - ) - } - - fn host_name(&self) -> String { - if let Some(ref hostname) = self.hostname { - if self.resolve_hostname { - return hostname - .to_socket_addrs() - .expect("Failed to resolve hostnames") - .last() - .expect("No hostnames found") - .to_string(); - } else { - return hostname.clone(); - } - } - if self.resolve_hostname { - RESOLVED_HOSTNAME.clone() - } else { - HOSTNAME.clone() - } - } -} diff --git a/autopush_rs/src/util/megaphone.rs b/autopush_rs/src/util/megaphone.rs deleted file mode 100644 index 7011c5c1..00000000 --- a/autopush_rs/src/util/megaphone.rs +++ /dev/null @@ -1,359 +0,0 @@ -use errors::Result; -use std::collections::HashMap; -use std::time::Duration; - -use reqwest; - -// A Service entry Key in a ServiceRegistry -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)] -struct ServiceKey(u32); - -// A list of services that a client is interested in and the last change seen -#[derive(Debug, Default)] -pub struct ClientServices { - service_list: Vec, - change_count: u32, -} - -#[derive(Debug)] -struct ServiceRegistry { - lookup: HashMap, - table: Vec, -} - -// Return result of the first delta call for a client given a full list of service id's and versions -#[derive(Debug)] -pub struct ServiceClientInit(pub ClientServices, pub Vec); - -impl ServiceRegistry { - fn new() -> ServiceRegistry { - ServiceRegistry { - lookup: HashMap::new(), - table: Vec::new(), - } - } - - // Add's a new service to the lookup table, returns the existing key if the service already - // exists - fn add_service(&mut self, service_id: String) -> ServiceKey { - if let Some(v) = self.lookup.get(&service_id) { - return ServiceKey(*v); - } - let i = self.table.len(); - self.table.push(service_id.clone()); - self.lookup.insert(service_id, i as u32); - ServiceKey(i as u32) - } - - fn lookup_id(&self, key: ServiceKey) -> Option { - self.table.get(key.0 as usize).cloned() - } - - fn lookup_key(&self, service_id: &str) -> Option { - self.lookup.get(service_id).cloned().map(ServiceKey) - } -} - -// An individual service and the current change count -#[derive(Debug)] -struct ServiceRevision { - change_count: u32, - service: ServiceKey, -} - -// A provided Service/Version used for `ChangeList` initialization, client comparisons, and -// outgoing deltas -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -pub struct Service { - service_id: String, - version: String, -} - -// Handy From impls for common hashmap to/from conversions -impl From<(String, String)> for Service { - fn from(val: (String, String)) -> Service { - Service { - service_id: val.0, - version: val.1, - } - } -} - -impl From for (String, String) { - fn from(svc: Service) -> (String, String) { - (svc.service_id, svc.version) - } -} - -impl Service { - pub fn from_hashmap(val: HashMap) -> Vec { - val.into_iter().map(|v| v.into()).collect() - } - - pub fn into_hashmap(service_vec: Vec) -> HashMap { - service_vec.into_iter().map(|v| v.into()).collect() - } -} - -// ServiceChangeTracker tracks the services, their change_count, and the service lookup registry -#[derive(Debug)] -pub struct ServiceChangeTracker { - service_list: Vec, - service_registry: ServiceRegistry, - service_versions: HashMap, - change_count: u32, -} - -#[derive(Deserialize)] -pub struct MegaphoneAPIResponse { - pub broadcasts: HashMap, -} - -impl ServiceChangeTracker { - /// Creates a new `ServiceChangeTracker` initialized with the provided `services`. - pub fn new(services: Vec) -> ServiceChangeTracker { - let mut svc_change_tracker = ServiceChangeTracker { - service_list: Vec::new(), - service_registry: ServiceRegistry::new(), - service_versions: HashMap::new(), - change_count: 0, - }; - for srv in services { - let key = svc_change_tracker - .service_registry - .add_service(srv.service_id); - svc_change_tracker.service_versions.insert(key, srv.version); - } - svc_change_tracker - } - - /// Creates a new `ServiceChangeTracker` initialized from a Megaphone API server version set - /// as provided as the fetch URL. - /// - /// This method uses a synchronous HTTP call. - pub fn with_api_services(url: &str, token: &str) -> reqwest::Result { - let client = reqwest::Client::builder() - .timeout(Duration::from_secs(1)) - .build()?; - let MegaphoneAPIResponse { broadcasts } = client - .get(url) - .header(reqwest::header::Authorization(token.to_string())) - .send()? - .error_for_status()? - .json()?; - let services = Service::from_hashmap(broadcasts); - Ok(ServiceChangeTracker::new(services)) - } - - /// Add a new service to the ServiceChangeTracker, triggering a change_count increase. - /// Note: If the service already exists, it will be updated instead. - pub fn add_service(&mut self, service: Service) -> u32 { - if let Ok(change_count) = self.update_service(service.clone()) { - return change_count; - } - self.change_count += 1; - let key = self.service_registry.add_service(service.service_id); - self.service_versions.insert(key, service.version); - self.service_list.push(ServiceRevision { - change_count: self.change_count, - service: key, - }); - self.change_count - } - - /// Update a `service` to a new revision, triggering a change_count increase. - /// - /// Returns an error if the `service` was never initialized/added. - pub fn update_service(&mut self, service: Service) -> Result { - let key = self.service_registry - .lookup_key(&service.service_id) - .ok_or("Service not found")?; - - if let Some(ver) = self.service_versions.get_mut(&key) { - if *ver == service.version { - return Ok(self.change_count); - } - *ver = service.version; - } else { - return Err("Service not found".into()); - } - - // Check to see if this service has been updated since initialization - let svc_index = self.service_list - .iter() - .enumerate() - .filter_map(|(i, svc)| if svc.service == key { Some(i) } else { None }) - .nth(0); - self.change_count += 1; - if let Some(svc_index) = svc_index { - let mut svc = self.service_list.remove(svc_index); - svc.change_count = self.change_count; - self.service_list.push(svc); - } else { - self.service_list.push(ServiceRevision { - change_count: self.change_count, - service: key, - }) - } - Ok(self.change_count) - } - - /// Returns the new service versions since the provided `client_set`. - pub fn change_count_delta(&self, client_set: &mut ClientServices) -> Option> { - if self.change_count <= client_set.change_count { - return None; - } - let mut svc_delta = Vec::new(); - for svc in self.service_list.iter().rev() { - if svc.change_count <= client_set.change_count { - break; - } - if !client_set.service_list.contains(&svc.service) { - continue; - } - if let Some(ver) = self.service_versions.get(&svc.service) { - if let Some(svc_id) = self.service_registry.lookup_id(svc.service) { - svc_delta.push(Service { - service_id: svc_id, - version: (*ver).clone(), - }); - } - } - } - client_set.change_count = self.change_count; - if svc_delta.is_empty() { - None - } else { - Some(svc_delta) - } - } - - /// Returns a delta for `services` that are out of date with the latest version and a new - /// `ClientSet``. - pub fn service_delta(&self, services: &[Service]) -> ServiceClientInit { - let mut svc_list = Vec::new(); - let mut svc_delta = Vec::new(); - for svc in services.iter() { - if let Some(svc_key) = self.service_registry.lookup_key(&svc.service_id) { - if let Some(ver) = self.service_versions.get(&svc_key) { - if *ver != svc.version { - svc_delta.push(Service { - service_id: svc.service_id.clone(), - version: (*ver).clone(), - }); - } - } - svc_list.push(svc_key); - } - } - ServiceClientInit( - ClientServices { - service_list: svc_list, - change_count: self.change_count, - }, - svc_delta, - ) - } - - /// Update a `ClientServices` to account for a new service. - /// - /// Returns services that have changed. - pub fn client_service_add_service( - &self, - client_service: &mut ClientServices, - services: &[Service], - ) -> Option> { - let mut svc_delta = self.change_count_delta(client_service) - .unwrap_or_default(); - for svc in services.iter() { - if let Some(svc_key) = self.service_registry.lookup_key(&svc.service_id) { - if let Some(ver) = self.service_versions.get(&svc_key) { - if *ver != svc.version { - svc_delta.push(Service { - service_id: svc.service_id.clone(), - version: (*ver).clone(), - }); - } - } - client_service.service_list.push(svc_key) - } - } - if svc_delta.is_empty() { - None - } else { - Some(svc_delta) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn make_service_base() -> Vec { - vec![ - Service { - service_id: String::from("svca"), - version: String::from("rev1"), - }, - Service { - service_id: String::from("svcb"), - version: String::from("revalha"), - }, - ] - } - - #[test] - fn test_service_change_tracker() { - let services = make_service_base(); - let client_services = services.clone(); - let mut svc_chg_tracker = ServiceChangeTracker::new(services); - let ServiceClientInit(mut client_svc, delta) = - svc_chg_tracker.service_delta(&client_services); - assert_eq!(delta.len(), 0); - assert_eq!(client_svc.change_count, 0); - assert_eq!(client_svc.service_list.len(), 2); - - svc_chg_tracker - .update_service(Service { - service_id: String::from("svca"), - version: String::from("rev2"), - }) - .ok(); - let delta = svc_chg_tracker.change_count_delta(&mut client_svc); - assert!(delta.is_some()); - let delta = delta.unwrap(); - assert_eq!(delta.len(), 1); - } - - #[test] - fn test_service_change_handles_new_services() { - let services = make_service_base(); - let client_services = services.clone(); - let mut svc_chg_tracker = ServiceChangeTracker::new(services); - let ServiceClientInit(mut client_svc, _) = svc_chg_tracker.service_delta(&client_services); - - svc_chg_tracker.add_service(Service { - service_id: String::from("svcc"), - version: String::from("revmega"), - }); - let delta = svc_chg_tracker.change_count_delta(&mut client_svc); - assert!(delta.is_none()); - - let delta = svc_chg_tracker - .client_service_add_service( - &mut client_svc, - &vec![ - Service { - service_id: String::from("svcc"), - version: String::from("revision_alpha"), - }, - ], - ) - .unwrap(); - assert_eq!(delta.len(), 1); - assert_eq!(delta[0].version, String::from("revmega")); - assert_eq!(client_svc.change_count, 1); - assert_eq!(svc_chg_tracker.service_list.len(), 1); - } -} diff --git a/autopush_rs/src/util/mod.rs b/autopush_rs/src/util/mod.rs deleted file mode 100644 index 47ffca91..00000000 --- a/autopush_rs/src/util/mod.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! Various small utilities accumulated over time for the WebPush server -use std::time::Duration; - -use futures::future::{Either, Future, IntoFuture}; -use tokio_core::reactor::{Handle, Timeout}; - -use errors::*; - -pub mod megaphone; -mod rc; -mod send_all; -pub mod timing; -mod user_agent; - -pub use self::rc::RcObject; -pub use self::send_all::MySendAll; -pub use self::timing::{ms_since_epoch, sec_since_epoch, us_since_epoch}; -pub use self::user_agent::parse_user_agent; - -/// Convenience future to time out the resolution of `f` provided within the -/// duration provided. -/// -/// If the `dur` is `None` then the returned future is equivalent to `f` (no -/// timeout) and otherwise the returned future will cancel `f` and resolve to an -/// error if the `dur` timeout elapses before `f` resolves. -pub fn timeout(f: F, dur: Option, handle: &Handle) -> MyFuture -where - F: Future + 'static, - F::Error: Into, -{ - let dur = match dur { - Some(dur) => dur, - None => return Box::new(f.map_err(|e| e.into())), - }; - let timeout = Timeout::new(dur, handle).into_future().flatten(); - Box::new(f.select2(timeout).then(|res| match res { - Ok(Either::A((item, _timeout))) => Ok(item), - Err(Either::A((e, _timeout))) => Err(e.into()), - Ok(Either::B(((), _item))) => Err("timed out".into()), - Err(Either::B((e, _item))) => Err(e.into()), - })) -} diff --git a/autopush_rs/src/util/rc.rs b/autopush_rs/src/util/rc.rs deleted file mode 100644 index ad82524f..00000000 --- a/autopush_rs/src/util/rc.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::cell::{RefCell, RefMut}; -use std::rc::Rc; - -use futures::{Poll, Sink, StartSend, Stream}; - -/// Helper object to turn `Rc>` into a `Stream` and `Sink` -/// -/// This is basically just a helper to allow multiple "owning" references to a -/// `T` which is both a `Stream` and a `Sink`. Similar to `Stream::split` in the -/// futures crate, but doesn't actually split it (and allows internal access). -pub struct RcObject(Rc>); - -impl RcObject { - pub fn new(t: T) -> RcObject { - RcObject(Rc::new(RefCell::new(t))) - } - - pub fn borrow_mut(&self) -> RefMut { - self.0.borrow_mut() - } -} - -impl Stream for RcObject { - type Item = T::Item; - type Error = T::Error; - - fn poll(&mut self) -> Poll, T::Error> { - self.0.borrow_mut().poll() - } -} - -impl Sink for RcObject { - type SinkItem = T::SinkItem; - type SinkError = T::SinkError; - - fn start_send(&mut self, msg: T::SinkItem) -> StartSend { - self.0.borrow_mut().start_send(msg) - } - - fn poll_complete(&mut self) -> Poll<(), T::SinkError> { - self.0.borrow_mut().poll_complete() - } - - fn close(&mut self) -> Poll<(), T::SinkError> { - self.0.borrow_mut().close() - } -} - -impl Clone for RcObject { - fn clone(&self) -> RcObject { - RcObject(self.0.clone()) - } -} diff --git a/autopush_rs/src/util/send_all.rs b/autopush_rs/src/util/send_all.rs deleted file mode 100644 index bc1bdec0..00000000 --- a/autopush_rs/src/util/send_all.rs +++ /dev/null @@ -1,91 +0,0 @@ -use futures::stream::Fuse; -use futures::{Async, AsyncSink, Future, Poll, Sink, Stream}; - -// This is a copy of `Future::forward`, except that it doesn't close the sink -// when it's finished. -pub struct MySendAll { - sink: Option, - stream: Option>, - buffered: Option, -} - -impl MySendAll -where - U: Sink, - T: Stream, - T::Error: From, -{ - #[allow(unused)] - pub fn new(t: T, u: U) -> MySendAll { - MySendAll { - sink: Some(u), - stream: Some(t.fuse()), - buffered: None, - } - } - - fn sink_mut(&mut self) -> &mut U { - self.sink - .as_mut() - .take() - .expect("Attempted to poll MySendAll after completion") - } - - fn stream_mut(&mut self) -> &mut Fuse { - self.stream - .as_mut() - .take() - .expect("Attempted to poll MySendAll after completion") - } - - fn take_result(&mut self) -> (T, U) { - let sink = self.sink - .take() - .expect("Attempted to poll MySendAll after completion"); - let fuse = self.stream - .take() - .expect("Attempted to poll MySendAll after completion"); - (fuse.into_inner(), sink) - } - - fn try_start_send(&mut self, item: T::Item) -> Poll<(), U::SinkError> { - debug_assert!(self.buffered.is_none()); - if let AsyncSink::NotReady(item) = try!(self.sink_mut().start_send(item)) { - self.buffered = Some(item); - return Ok(Async::NotReady); - } - Ok(Async::Ready(())) - } -} - -impl Future for MySendAll -where - U: Sink, - T: Stream, - T::Error: From, -{ - type Item = (T, U); - type Error = T::Error; - - fn poll(&mut self) -> Poll<(T, U), T::Error> { - // If we've got an item buffered already, we need to write it to the - // sink before we can do anything else - if let Some(item) = self.buffered.take() { - try_ready!(self.try_start_send(item)) - } - - loop { - match try!(self.stream_mut().poll()) { - Async::Ready(Some(item)) => try_ready!(self.try_start_send(item)), - Async::Ready(None) => { - try_ready!(self.sink_mut().poll_complete()); - return Ok(Async::Ready(self.take_result())); - } - Async::NotReady => { - try_ready!(self.sink_mut().poll_complete()); - return Ok(Async::NotReady); - } - } - } - } -} diff --git a/autopush_rs/src/util/timing.rs b/autopush_rs/src/util/timing.rs deleted file mode 100644 index 7c7a7e47..00000000 --- a/autopush_rs/src/util/timing.rs +++ /dev/null @@ -1,18 +0,0 @@ -use chrono::prelude::*; - -/// Get the time since the UNIX epoch in seconds -pub fn sec_since_epoch() -> u64 { - Utc::now().timestamp() as u64 -} - -/// Get the time since the UNIX epoch in milliseconds -pub fn ms_since_epoch() -> u64 { - Utc::now().timestamp_millis() as u64 -} - -/// Get the time since the UNIX epoch in microseconds -#[allow(dead_code)] -pub fn us_since_epoch() -> u64 { - let now = Utc::now(); - (now.timestamp() as u64) * 1_000_000 + (now.timestamp_subsec_micros() as u64) -} diff --git a/autopush_rs/src/util/user_agent.rs b/autopush_rs/src/util/user_agent.rs deleted file mode 100644 index 9b337db9..00000000 --- a/autopush_rs/src/util/user_agent.rs +++ /dev/null @@ -1,92 +0,0 @@ -use woothee::parser::{Parser, WootheeResult}; - -// List of valid user-agent attributes to keep, anything not in this -// list is considered 'Other'. We log the user-agent on connect always -// to retain the full string, but for DD more tags are expensive so we -// limit to these. -const VALID_UA_BROWSER: &[&str] = &["Chrome", "Firefox", "Safari", "Opera"]; - -// See dataset.rs in https://github.com/woothee/woothee-rust for the -// full list (WootheeResult's 'os' field may fall back to its 'name' -// field). Windows has many values and we only care that its Windows -const VALID_UA_OS: &[&str] = &["Firefox OS", "Linux", "Mac OSX"]; - -pub fn parse_user_agent<'a>( - parser: &'a Parser, - agent: &str, -) -> (WootheeResult<'a>, &'a str, &'a str) { - let wresult = parser.parse(&agent).unwrap_or_else(|| WootheeResult { - name: "", - category: "", - os: "", - os_version: "".to_string(), - browser_type: "", - version: "".to_string(), - vendor: "", - }); - - // Determine a base os/browser for metrics' tags - let metrics_os = if wresult.os.starts_with("Windows") { - "Windows" - } else if VALID_UA_OS.contains(&wresult.os) { - wresult.os - } else { - "Other" - }; - let metrics_browser = if VALID_UA_BROWSER.contains(&wresult.name) { - wresult.name - } else { - "Other" - }; - (wresult, metrics_os, metrics_browser) -} - -#[cfg(test)] -mod tests { - use woothee::parser::Parser; - - use super::parse_user_agent; - - #[test] - fn test_linux() { - let agent = r#"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.2) Gecko/20090807 Mandriva Linux/1.9.1.2-1.1mud2009.1 (2009.1) Firefox/3.5.2 FirePHP/0.3,gzip(gfe),gzip(gfe)"#; - let parser = Parser::new(); - let (ua_result, metrics_os, metrics_browser) = parse_user_agent(&parser, &agent); - assert_eq!(metrics_os, "Linux"); - assert_eq!(ua_result.os, "Linux"); - assert_eq!(metrics_browser, "Firefox"); - } - - #[test] - fn test_windows() { - let agent = r#"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 (.NET CLR 3.5.30729)"#; - let parser = Parser::new(); - let (ua_result, metrics_os, metrics_browser) = parse_user_agent(&parser, &agent); - assert_eq!(metrics_os, "Windows"); - assert_eq!(ua_result.os, "Windows 7"); - assert_eq!(metrics_browser, "Firefox"); - } - - #[test] - fn test_osx() { - let agent = - r#"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.5; rv:2.1.1) Gecko/ Firefox/5.0.1"#; - let parser = Parser::new(); - let (ua_result, metrics_os, metrics_browser) = parse_user_agent(&parser, &agent); - assert_eq!(metrics_os, "Mac OSX"); - assert_eq!(ua_result.os, "Mac OSX"); - assert_eq!(metrics_browser, "Firefox"); - } - - #[test] - fn test_other() { - let agent = - r#"BlackBerry9000/4.6.0.167 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/102"#; - let parser = Parser::new(); - let (ua_result, metrics_os, metrics_browser) = parse_user_agent(&parser, &agent); - assert_eq!(metrics_os, "Other"); - assert_eq!(ua_result.os, "BlackBerry"); - assert_eq!(metrics_browser, "Other"); - assert_eq!(ua_result.name, "UNKNOWN"); - } -} diff --git a/setup.py b/setup.py index 1d621deb..882f33af 100644 --- a/setup.py +++ b/setup.py @@ -10,31 +10,10 @@ with io.open(os.path.join(here, 'CHANGELOG.md'), encoding='utf8') as f: CHANGES = f.read() -WITH_RUST = os.environ.get('WITH_RUST', 'true') - extra_options = { "packages": find_packages(), } -if WITH_RUST.lower() not in ('false', '0'): - def build_native(spec): - cmd = ['cargo', 'build'] - if WITH_RUST.lower() == 'release': - cmd.append('--release') - - spec.add_external_build( - cmd=cmd, - path='./autopush_rs' - ) - - extra_options.update( - setup_requires=['milksnake'], - install_requires=['milksnake'], - milksnake_tasks=[ - build_native - ] -) - setup(name="AutoPush", version=__version__, description='SimplePush Server', diff --git a/tox.ini b/tox.ini index 09d1b285..c0eac9f3 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ envlist = py27,pypy,flake8,py36-mypy [testenv] deps = -rtest-requirements.txt usedevelop = True -passenv = SKIP_INTEGRATION WITH_RUST +passenv = SKIP_INTEGRATION commands = nosetests {posargs} autopush install_command = pip install --pre {opts} {packages}