diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53eaa21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..332d81f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1414 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "arrayvec" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "atty" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "autocfg" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "backtrace" +version = "0.3.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "base64" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bumpalo" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byteorder" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cc" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "chrono" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "clap" +version = "2.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.1 (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.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-channel" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-deque" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-queue" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-utils" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "env_logger" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", + "humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure_derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fnv" +version = "1.0.6" +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.2.0 (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-channel-preview" +version = "0.3.0-alpha.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "futures-core-preview" +version = "0.3.0-alpha.19" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures-executor-preview" +version = "0.3.0-alpha.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "futures-io-preview" +version = "0.3.0-alpha.19" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures-preview" +version = "0.3.0-alpha.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-channel-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-executor-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-io-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "futures-sink-preview" +version = "0.3.0-alpha.19" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures-util-preview" +version = "0.3.0-alpha.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-channel-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-io-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "h2" +version = "0.2.0-alpha.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", + "indexmap 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-codec 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-sync 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "half" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "http" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "indexmap" +version = "1.2.0" +source = "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.62 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itoa" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "js-sys" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "wasm-bindgen 0.2.51 (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 = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lock_api" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memchr" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memmap" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memoffset" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mio" +version = "0.6.19" +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)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (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.33 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mio-uds" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (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.33 (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 = "net2" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nodrop" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "nom" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-integer" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num_cpus" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pin-project" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "pin-project-internal 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pin-utils" +version = "0.1.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pretty_env_logger" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro-error" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro2" +version = "0.4.30" +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 = "proc-macro2" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quick-error" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "regex" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ring" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "web-sys 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc_version" +version = "0.2.3" +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 = "rustls" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ring 0.16.9 (registry+https://github.com/rust-lang/crates.io-index)", + "sct 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "webpki 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "scopeguard" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "sct" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ring 0.16.9 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.7.0 (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 = "serde" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_cbor" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "half 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "smallvec" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "sourcefile" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "string" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "structopt" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt-derive 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "structopt-derive" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-error 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "0.15.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (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 = "syn" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synstructure" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.44 (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 = "termcolor" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "wincolor 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "time" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-codec 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-fs 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-macros 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-net 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-sync 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-codec" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-executor" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-sync 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-fs" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-sync 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-io" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-macros" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-net" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-codec 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-sync 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-rustls" +version = "0.12.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "rustls 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "webpki 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-sync" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-timer" +version = "0.3.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-sync 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tracerbench-recorded-response-server" +version = "0.1.0" +dependencies = [ + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "h2 0.2.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ring 0.16.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_cbor 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-rustls 0.12.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", + "tracerbench-recorded-response-set 0.1.0", + "tracerbench-socks-proxy 0.1.0", + "webpki 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tracerbench-recorded-response-set" +version = "0.1.0" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", + "memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "tracerbench-request-key 0.1.0", +] + +[[package]] +name = "tracerbench-request-key" +version = "0.1.0" +dependencies = [ + "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_cbor 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tracerbench-serve" +version = "0.1.0" +dependencies = [ + "pretty_env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tracerbench-recorded-response-server 0.1.0", +] + +[[package]] +name = "tracerbench-socks-proxy" +version = "0.1.0" +dependencies = [ + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tracing" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing-attributes 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tracing-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-segmentation" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-width" +version = "0.1.6" +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 = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "untrusted" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "wasm-bindgen" +version = "0.2.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-macro 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-shared 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-macro-support 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-backend 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-shared 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.51" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "wasm-bindgen-webidl" +version = "0.2.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-backend 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "weedle 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "web-sys" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "js-sys 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", + "sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-webidl 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "webpki" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ring 0.16.9 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "weedle" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nom 4.2.3 (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.8" +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-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.8 (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 = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util 0.1.2 (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)", +] + +[metadata] +"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba" +"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" +"checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875" +"checksum backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)" = "690a62be8920ccf773ee00ef0968649b0e724cda8bd5b12286302b4ae955fdf5" +"checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b" +"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" +"checksum bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8a606a02debe2813760609f57a64a2ffd27d9fdf5b2f133eaca0b248dd92cdd2" +"checksum bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad807f2fc2bf185eeb98ff3a901bd46dc5ad58163d0fa4577ba0d25674d71708" +"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" +"checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +"checksum cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "4fc9a35e1f4290eb9e5fc54ba6cf40671ed2a2514c3eeb2b2a908dda2ea5a1be" +"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +"checksum chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e8493056968583b0193c1bb04d6f7684586f3726992d6c573261941a895dbd68" +"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c8ec7fcd21571dc78f96cc96243cab8d8f035247c3efd16c687be154c3fa9efa" +"checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71" +"checksum crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fedcd6772e37f3da2a9af9bf12ebe046c0dfe657992377b4df982a2b54cd37a9" +"checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" +"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" +"checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" +"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" +"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" +"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" +"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-channel-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)" = "d5e5f4df964fa9c1c2f8bddeb5c3611631cacd93baf810fc8bb2fb4b495c263a" +"checksum futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)" = "b35b6263fb1ef523c3056565fa67b1d16f0a8604ff12b11b08c25f28a734c60a" +"checksum futures-executor-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)" = "75236e88bd9fe88e5e8bfcd175b665d0528fe03ca4c5207fabc028c8f9d93e98" +"checksum futures-io-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)" = "f4914ae450db1921a56c91bde97a27846287d062087d4a652efc09bb3a01ebda" +"checksum futures-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)" = "3b1dce2a0267ada5c6ff75a8ba864b4e679a9e2aa44262af7a3b5516d530d76e" +"checksum futures-sink-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)" = "86f148ef6b69f75bb610d4f9a2336d4fc88c4b5b67129d1a340dd0fd362efeec" +"checksum futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)" = "5ce968633c17e5f97936bd2797b6e38fb56cf16a7422319f7ec2e30d3c470e8d" +"checksum h2 0.2.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0f107db1419ef8271686187b1a5d47c6431af4a7f4d98b495e7b7fc249bb0a78" +"checksum half 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a4e3d89dc66597bc3b70f24720bfb53cf0e086fbfbe63f2498e4edb95b36500c" +"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +"checksum http 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "372bcb56f939e449117fb0869c2e8fd8753a8223d92a172c6e808cf123a5b6e4" +"checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +"checksum indexmap 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a61202fbe46c4a951e9404a720a0180bcf3212c750d735cb5c4ba4dc551299f3" +"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" +"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +"checksum js-sys 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)" = "2cc9a97d7cec30128fd8b28a7c1f9df1c001ceb9b441e2b755e24130a6b43c79" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" +"checksum lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8912e782533a93a167888781b836336a6ca5da6175c05944c86cf28c31104dc" +"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" +"checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +"checksum memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f" +"checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" +"checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" +"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" +"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" +"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" +"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" +"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" +"checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" +"checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" +"checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" +"checksum pin-project 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3d9156ea5979ae30ecc0460cd848738daf24cfb89eb11a41e0c369ba1f0e6aeb" +"checksum pin-project-internal 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1a375fffcd7bf53d8302fb95c1e2f3e0a1a92bd57edcab796f26f9e527c2f3da" +"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" +"checksum pretty_env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "717ee476b1690853d222af4634056d830b5197ffd747726a9a1eee6da9f49074" +"checksum proc-macro-error 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aeccfe4d5d8ea175d5f0e4a2ad0637e0f4121d63bd99d356fb1f39ab2e7c6097" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +"checksum proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "90cf5f418035b98e655e9cdb225047638296b862b42411c4e45bb88d700f7fc0" +"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" +"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +"checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" +"checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" +"checksum ring 0.16.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6747f8da1f2b1fabbee1aaa4eb8a11abf9adef0bf58a41cee45db5d59cecdfac" +"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +"checksum rustls 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b25a18b1bf7387f0145e7f8324e700805aade3842dd3db2e74e4cdeb4677c09e" +"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" +"checksum sct 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" +"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 serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd" +"checksum serde_cbor 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7081ed758ec726a6ed8ee7e92f5d3f6e6f8c3901b1f972e3a4a2f2599fad14f" +"checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e" +"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +"checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" +"checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" +"checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +"checksum string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d" +"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +"checksum structopt 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe8d3289b63ef2f196d89e7701f986583c0895e764b78f052a55b9b5d34d84a" +"checksum structopt-derive 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f3add731f5b4fb85931d362a3c92deb1ad7113649a8d51701fb257673705f122" +"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" +"checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" +"checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e" +"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" +"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" +"checksum tokio 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1f17f5d6ab0f35c1506678b28fb1798bdf74fcb737e9843c7b17b73e426eba38" +"checksum tokio-codec 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9f5d22fd1e84bd4045d28813491cb7d7caae34d45c80517c2213f09a85e8787a" +"checksum tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9ee9ceecf69145923834ea73f32ba40c790fd877b74a7817dd0b089f1eb9c7c8" +"checksum tokio-fs 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bf85e16971e06e680c622e0c1b455be94b086275c5ddcd6d4a83a2bfbb83cda" +"checksum tokio-io 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "112784d5543df30660b04a72ca423bfbd90e8bb32f94dcf610f15401218b22c5" +"checksum tokio-macros 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "86b616374bcdadd95974e1f0dfca07dc913f1163c53840c0d664aca35114964e" +"checksum tokio-net 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a441682cd32f3559383112c4a7f372f5c9fa1950c5cf8c8dd05274a2ce8c2654" +"checksum tokio-rustls 0.12.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "03695b405a28febad121ce266561284d6d79645fdd53d679c20e8a34e1abffd9" +"checksum tokio-sync 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4f1aaeb685540f7407ea0e27f1c9757d258c7c6bf4e3eb19da6fc59b747239d2" +"checksum tokio-timer 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b97c1587fe71018eb245a4a9daa13a5a3b681bbc1f7fdadfe24720e141472c13" +"checksum tracing 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c21ff9457accc293386c20e8f754d0b059e67e325edf2284f04230d125d7e5ff" +"checksum tracing-attributes 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3ff978fd9c9afe2cc9c671e247713421c6406b3422305cbdce5de695d3ab4c3c" +"checksum tracing-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "528c8ebaaa16cdac34795180b046c031775b0d56402704d98c096788f33d646a" +"checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" +"checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +"checksum untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60369ef7a31de49bcb3f6ca728d4ba7300d9a1658f94c727d4cab8c8d9f4aece" +"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" +"checksum wasm-bindgen 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "cd34c5ba0d228317ce388e87724633c57edca3e7531feb4e25e35aaa07a656af" +"checksum wasm-bindgen-backend 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "927196b315c23eed2748442ba675a4c54a1a079d90d9bdc5ad16ce31cf90b15b" +"checksum wasm-bindgen-macro 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "92c2442bf04d89792816650820c3fb407af8da987a9f10028d5317f5b04c2b4a" +"checksum wasm-bindgen-macro-support 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "9c075d27b7991c68ca0f77fe628c3513e64f8c477d422b859e03f28751b46fc5" +"checksum wasm-bindgen-shared 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "83d61fe986a7af038dd8b5ec660e5849cbd9f38e7492b9404cc48b2b4df731d1" +"checksum wasm-bindgen-webidl 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "9b979afb0535fe4749906a674082db1211de8aef466331d43232f63accb7c07c" +"checksum web-sys 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)" = "c84440699cd02ca23bed6f045ffb1497bc18a3c2628bd13e2093186faaaacf6b" +"checksum webpki 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d7e664e770ac0110e2384769bcc59ed19e329d81f555916a6e072714957b81b4" +"checksum weedle 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bb43f70885151e629e2a19ce9e50bd730fd436cfd4b666894c9ce4de9141164" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +"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-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +"checksum wincolor 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96f5016b18804d24db43cebf3c77269e7569b8954a8464501c216cc5e070eaa9" +"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..36df272 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[workspace] +members = [ + "tracerbench-serve", + "request-key", + "recorded-response-set", + "recorded-response-server", + "socks-proxy" +] diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..a11ebed --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,11 @@ +BSD 2-CLAUSE LICENSE + +Copyright 2019 LinkedIn Corporation and Contributors. All Rights Reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..094b749 --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +# tracerbench-serve + +Serves recorded response sets for benchmarking. + +## recorded response set + +Serde deserialize format: + +Recorded response sets +Seq (len 6) + Body table + Header name table + Header value table + Headers table + Response table + Recorded response set table + +Body table: +Seq of + Bytes + +Header name table: +Seq of + String + +Header value table: +Seq of + String + +Headers table +Seq of + Seq of + ( + usize, // name table index + usize, // value table index + ) + +Response table +Seq of + ( + u16, // status + usize, // headers table index + Option, // body table index + ) + +Response set table +Seq of + Map + socksPort: u16, + name: String, + entryKey: String, + requestKeyProgram: Request key, + requestKeyMap: + Map String: usize // key to response_index + +Request key +Seq + literals + Bytes + +literals + Map + type: String, + content: Value + diff --git a/recorded-response-server/Cargo.toml b/recorded-response-server/Cargo.toml new file mode 100644 index 0000000..0640637 --- /dev/null +++ b/recorded-response-server/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "tracerbench-recorded-response-server" +version = "0.1.0" +authors = ["Kris Selden "] +license = "BSD-2-Clause" +edition = "2018" + +[dependencies] +base64 = "0.10" +bytes = "0.4" +futures-preview = { version = "0.3.0-alpha.19" } +h2 = { version = "=0.2.0-alpha.3", features = ["unstable-stream", "unstable"] } +http = "0.1" +log = "0.4" +memmap = "0.7" +ring = "0.16" +serde = "1" +serde_cbor = "0.10" +tokio = "=0.2.0-alpha.6" +tokio-rustls = "0.12.0-alpha" +tracerbench-recorded-response-set = { path = "../recorded-response-set" } +tracerbench-socks-proxy = { path = "../socks-proxy" } +webpki = "0.21" + diff --git a/recorded-response-server/src/config/mod.rs b/recorded-response-server/src/config/mod.rs new file mode 100644 index 0000000..fcc91e2 --- /dev/null +++ b/recorded-response-server/src/config/mod.rs @@ -0,0 +1,57 @@ +mod util; + +use std::io; +use std::path::PathBuf; +use std::sync::Arc; +use tokio_rustls::rustls; +use tracerbench_recorded_response_set::RecordedResponseSets; +use util::*; + +/// Server config +pub struct Config { + /// Value for chrome switch ignore-certificate-errors-spki-list + /// BASE64(SHA256(cert.subjectPublicKeyInfo))) + pub spki_digest: String, + /// Shared config for a TLS acceptor + pub tls_config: Arc, + /// Recorded response sets + pub response_sets: RecordedResponseSets, +} + +impl Config { + pub fn new( + spki_digest: String, + tls_config: Arc, + response_sets: RecordedResponseSets, + ) -> Self { + Config { + spki_digest, + tls_config, + response_sets, + } + } + + pub fn from_parts( + cert_chain: Vec, + private_key: rustls::PrivateKey, + response_sets: RecordedResponseSets, + ) -> Result { + Ok(Self::new( + spki_digest(cert_chain[0].as_ref())?, + build_tls_config(cert_chain, private_key)?, + response_sets, + )) + } + + pub fn from_args( + cert_pem: &PathBuf, + key_pem: &PathBuf, + response_sets_cbor: &PathBuf, + ) -> Result { + Self::from_parts( + read_cert_pem(cert_pem)?, + read_key_pem(key_pem)?, + read_response_set_cbor(response_sets_cbor)?, + ) + } +} diff --git a/recorded-response-server/src/config/util.rs b/recorded-response-server/src/config/util.rs new file mode 100644 index 0000000..615ca51 --- /dev/null +++ b/recorded-response-server/src/config/util.rs @@ -0,0 +1,132 @@ +use memmap::Mmap; +use ring::digest::digest; +use ring::digest::SHA256; +use std::error; +use std::fs::File; +use std::io::{BufReader, Error, ErrorKind}; +use std::path::PathBuf; +use std::sync::Arc; +use tokio_rustls::rustls::internal::pemfile; +use tokio_rustls::rustls::{Certificate, NoClientAuth, PrivateKey, ServerConfig}; +use tracerbench_recorded_response_set::RecordedResponseSets; +use webpki::trust_anchor_util::cert_der_as_trust_anchor; + +static ALPN_H2: &[u8] = b"h2"; + +pub(super) fn build_tls_config( + certs: Vec, + key: PrivateKey, +) -> Result, Error> { + let mut config = ServerConfig::new(NoClientAuth::new()); + config.set_single_cert(certs, key).map_err(invalid_input)?; + config.alpn_protocols.push(ALPN_H2.to_vec()); + Ok(Arc::new(config)) +} + +pub(super) fn read_cert_pem(path: &PathBuf) -> Result, Error> { + let file = File::open(path)?; + let mut reader = BufReader::new(&file); + if let Ok(certs) = pemfile::certs(&mut reader) { + if certs.is_empty() { + Err(missing_certificate(path)) + } else { + Ok(certs) + } + } else { + Err(invalid_pem_file(path)) + } +} + +pub(super) fn read_key_pem(path: &PathBuf) -> Result { + let file = File::open(path)?; + let mut reader = BufReader::new(&file); + if let Ok(mut private_keys) = pemfile::rsa_private_keys(&mut reader) { + if private_keys.is_empty() { + Err(missing_rsa_private_key(path)) + } else { + Ok(private_keys.remove(0)) + } + } else { + Err(invalid_pem_file(path)) + } +} + +pub(super) fn read_response_set_cbor(path: &PathBuf) -> Result { + let file = File::open(path)?; + let mmap = unsafe { Mmap::map(&file)? }; + let response_sets = serde_cbor::from_slice(&mmap).map_err(invalid_data)?; + Ok(response_sets) +} + +/// The base64 encoded SHA256 digest of subject public key info. +/// This is needed for the chrome command line switch ignore-certificate-errors-spki-list +pub(super) fn spki_digest(cert_der: &[u8]) -> Result { + let trust_anchor = cert_der_as_trust_anchor(cert_der).map_err(invalid_data)?; + // unfortunately the only public api exposed to get the subjectPublicKeyInfo + // is this one, and it is only the V part of the DER TLV + // and we need the TL part + let value_len = trust_anchor.spki.len(); + let be_bytes = usize::to_be_bytes(value_len); + let start = be_bytes.iter().position(|&b| b > 0).unwrap(); + let length_bytes = &be_bytes[start..]; + + let length_byte_length = length_bytes.len() as u8; + let len_127_or_less = length_byte_length == 1 && length_bytes[0] < 0x80; + let capacity = if len_127_or_less { + value_len + 2 + } else { + value_len + 2 + length_byte_length as usize + }; + + let mut seq_tlv: Vec = Vec::with_capacity(capacity); + + // SEQUENCE TAG + seq_tlv.push(0x30); + if len_127_or_less { + // if length is < 128 store length directly + seq_tlv.push(length_bytes[0]) + } else { + // if length is >= 128 set the hi bit and the byte length of the length + seq_tlv.push(length_byte_length | 0x80); + // add the length bytes + seq_tlv.extend_from_slice(length_bytes); + } + + // add the value + seq_tlv.extend_from_slice(trust_anchor.spki); + + let spki_digest = digest(&SHA256, &seq_tlv); + Ok(base64::encode(spki_digest.as_ref())) +} + +fn missing_certificate(path: &PathBuf) -> Error { + invalid_input(format!( + "Missing CERTIFICATE section in PEM file: {:?}", + path + )) +} + +fn missing_rsa_private_key(path: &PathBuf) -> Error { + invalid_input(format!( + "Missing RSA PRIVATE KEY section in PEM file: {:?}", + path + )) +} + +fn invalid_pem_file(path: &PathBuf) -> Error { + invalid_data(format!("Invalid PEM file: {:?}", path)) +} + +fn invalid_data(err: E) -> Error +where + E: Into>, +{ + Error::new(ErrorKind::InvalidData, err) +} + +fn invalid_input(err: E) -> Error +where + E: Into>, +{ + Error::new(ErrorKind::InvalidInput, err) +} diff --git a/recorded-response-server/src/lib.rs b/recorded-response-server/src/lib.rs new file mode 100644 index 0000000..eea7dde --- /dev/null +++ b/recorded-response-server/src/lib.rs @@ -0,0 +1,10 @@ +#![warn(rust_2018_idioms)] +#![warn(clippy::all)] + +mod config; +mod server; +mod servers; + +pub use config::Config; +pub use server::Server; +pub use servers::Servers; diff --git a/recorded-response-server/src/server/error.rs b/recorded-response-server/src/server/error.rs new file mode 100644 index 0000000..606f822 --- /dev/null +++ b/recorded-response-server/src/server/error.rs @@ -0,0 +1,59 @@ +use std::error; +use std::fmt; +use std::io; +use tokio_rustls::rustls::TLSError; +use tracerbench_socks_proxy::SocksError; + +#[derive(Debug)] +pub(super) enum ServerError { + IO(io::Error), + Socks(SocksError), + TLS(TLSError), + H2(h2::Error), +} + +impl From for ServerError { + fn from(err: io::Error) -> ServerError { + ServerError::IO(err) + } +} + +impl From for ServerError { + fn from(err: SocksError) -> ServerError { + ServerError::Socks(err) + } +} + +impl From for ServerError { + fn from(err: TLSError) -> ServerError { + ServerError::TLS(err) + } +} + +impl From for ServerError { + fn from(err: h2::Error) -> ServerError { + ServerError::H2(err) + } +} + +impl error::Error for ServerError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + ServerError::IO(ref err) => error::Error::source(err), + ServerError::Socks(ref err) => error::Error::source(err), + ServerError::TLS(ref err) => error::Error::source(err), + ServerError::H2(ref err) => error::Error::source(err), + } + } +} + +impl fmt::Display for ServerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + ServerError::IO(ref err) => err.fmt(f), + ServerError::Socks(ref err) => err.fmt(f), + ServerError::TLS(ref err) => err.fmt(f), + ServerError::H2(ref err) => err.fmt(f), + } + } +} diff --git a/recorded-response-server/src/server/mod.rs b/recorded-response-server/src/server/mod.rs new file mode 100644 index 0000000..d78c4a9 --- /dev/null +++ b/recorded-response-server/src/server/mod.rs @@ -0,0 +1,82 @@ +mod error; +mod serve; + +use error::ServerError; +use serve::serve_h2; +use std::io; +use std::net::{Ipv4Addr, SocketAddr}; +use std::sync::Arc; +use tokio::net::TcpListener; +use tokio::net::TcpStream; +use tokio_rustls::rustls; +use tokio_rustls::TlsAcceptor; +use tracerbench_recorded_response_set::RecordedResponseSet; +use tracerbench_socks_proxy::socks5_handshake; + +/// Server listens on a port with a socks proxy -> tls -> h2 +/// serving recorded responses from a set +pub struct Server { + tls_config: Arc, + response_set: Arc, +} + +impl Server { + pub fn new( + tls_config: Arc, + response_set: Arc, + ) -> Self { + Server { + tls_config, + response_set, + } + } + + pub fn name(&self) -> &str { + self.response_set.name() + } + + pub fn addr(&self) -> SocketAddr { + SocketAddr::new( + Ipv4Addr::new(127, 0, 0, 1).into(), + self.response_set.socks_port(), + ) + } + + pub async fn start(&self) -> Result<(), io::Error> { + let mut listener = TcpListener::bind(self.addr()).await?; + + println!( + "response set {} socks proxy server listening at {}", + self.name(), + self.addr() + ); + + loop { + match listener.accept().await { + Ok((socket, _peer_addr)) => self.spawn(socket), + Err(err) => log::warn!("failed to accept client {:?}", err), + } + } + } + + fn spawn(&self, socket: TcpStream) { + let tls_config = self.tls_config.clone(); + let response_set = self.response_set.clone(); + tokio::spawn(async move { + if let Err(err) = handle_tcp_connection(socket, tls_config, response_set).await { + log::warn!("{:?}", err); + } + }); + } +} + +async fn handle_tcp_connection( + socket: TcpStream, + tls_config: Arc, + response_set: Arc, +) -> Result<(), ServerError> { + let socket = socks5_handshake(socket).await?; + let tls_socket = TlsAcceptor::from(tls_config).accept(socket).await?; + serve_h2(tls_socket, response_set).await?; + Ok(()) +} diff --git a/recorded-response-server/src/server/serve.rs b/recorded-response-server/src/server/serve.rs new file mode 100644 index 0000000..96ba205 --- /dev/null +++ b/recorded-response-server/src/server/serve.rs @@ -0,0 +1,248 @@ +use bytes::Bytes; +use futures::future::poll_fn; +use h2::server; +use h2::server::SendResponse; +use h2::RecvStream; +use h2::SendStream; +use http::header::HeaderName; +use http::header::ACCEPT; +use http::request::Parts; +use http::Method; +use http::Request; +use http::Response; +use http::StatusCode; +use http::Uri; +use std::sync::Arc; +use tokio::io::{AsyncRead, AsyncWrite}; +use tracerbench_recorded_response_set::RecordedResponseSet; + +static EVENT_STREAM: &[u8] = b"text/event-stream"; + +/// Serves the H2 connection with the specified response set. +pub(super) async fn serve_h2(socket: S, set: Arc) -> Result<(), h2::Error> +where + S: AsyncRead + AsyncWrite + Unpin, +{ + let mut connection = server::handshake(socket).await?; + log::debug!("{} HTTP2 connection bound", set.name()); + while let Some(result) = connection.accept().await { + let (request, send_response) = result?; + spawn_accept_request(set.clone(), request, send_response); + } + Ok(()) +} + +fn spawn_accept_request( + response_set: Arc, + request: Request, + send_response: SendResponse, +) { + tokio::spawn(async move { + let (head, body) = request.into_parts(); + RequestAcceptor::new(response_set, head) + .accept(body, send_response) + .await + }); +} + +struct RequestAcceptor { + response_set: Arc, + head: Parts, +} + +impl RequestAcceptor { + fn new(response_set: Arc, head: Parts) -> Self { + RequestAcceptor { head, response_set } + } + + fn name(&self) -> &str { + self.response_set.name() + } + + fn method(&self) -> &Method { + &self.head.method + } + + fn uri(&self) -> &Uri { + &self.head.uri + } + + fn is_get(&self) -> bool { + self.head.method == Method::GET + } + + fn is_head(&self) -> bool { + self.head.method == Method::HEAD + } + + fn header_equals(&self, name: HeaderName, value: &[u8]) -> bool { + if let Some(header) = self.head.headers.get(name) { + header == value + } else { + false + } + } + + fn is_server_sent_events(&self) -> bool { + self.is_get() && self.header_equals(ACCEPT, EVENT_STREAM) + } + + fn get_response(&self) -> Option<(Response<()>, Option)> { + let method = if self.is_head() { + &Method::GET + } else { + self.method() + }; + self.response_set.response_for(method, self.uri()) + } + + async fn accept(&self, body: RecvStream, send_response: SendResponse) { + log::debug!("{} ACCEPT {} {}", self.name(), self.method(), self.uri()); + if let Err(err) = self.handle(body, send_response).await { + log::warn!( + "{} ERROR {} {} {}", + self.name(), + self.method(), + self.uri(), + err + ); + } + } + + async fn handle( + &self, + body: RecvStream, + send_response: SendResponse, + ) -> Result<(), h2::Error> { + // server-sent events request we just keep open + // until the client closes + // we currently dont support sending any events + if self.is_server_sent_events() { + log::debug!("{} Server-Sent Events {}", self.name(), self.uri()); + self.respond_and_wait_for_reset(send_response).await?; + return Ok(()); + } + + // we want to consume the body before replying + // even though we don't currently use the body for + // (has not mattered for initial render benchmarking). + self.read_body(body).await?; + + self.respond(send_response).await?; + + Ok(()) + } + + async fn read_body(&self, mut body: RecvStream) -> Result, h2::Error> { + let mut total = None; + while let Some(result) = poll_fn(|cx| body.poll_data(cx)).await { + let chunk = result?; + total = Some(total.unwrap_or(0) + chunk.len()); + } + Ok(total) + } + + async fn respond(&self, send_response: SendResponse) -> Result<(), h2::Error> { + if let Some((response, maybe_body)) = self.get_response() { + if let Some(body) = maybe_body { + if !self.is_head() { + return self.respond_with_body(send_response, response, body).await; + } + } + self.respond_with_no_body(send_response, response) + } else { + self.respond_with_not_found(send_response) + } + } + + async fn respond_with_body( + &self, + mut respond: SendResponse, + response: Response<()>, + body: Bytes, + ) -> Result<(), h2::Error> { + let status = response.status().as_u16(); + + let send_stream = respond.send_response(response, false)?; + let sent = self.send_body(send_stream, body).await?; + + log::debug!( + "{} {} {} {} {}", + self.name(), + status, + self.method(), + self.uri(), + sent + ); + + Ok(()) + } + + fn respond_with_no_body( + &self, + mut respond: SendResponse, + response: Response<()>, + ) -> Result<(), h2::Error> { + let status = response.status().as_u16(); + + respond.send_response(response, true)?; + + log::debug!( + "{} {} {} {} None", + self.name(), + status, + self.method(), + self.uri() + ); + + Ok(()) + } + + fn respond_with_not_found(&self, mut respond: SendResponse) -> Result<(), h2::Error> { + let mut response = Response::new(()); + *response.status_mut() = StatusCode::NOT_FOUND; + + respond.send_response(response, true)?; + + log::debug!("{} 404 {} {} None", self.name(), self.method(), self.uri()); + + Ok(()) + } + + async fn respond_and_wait_for_reset( + &self, + mut respond: SendResponse, + ) -> Result<(), h2::Error> { + let mut send_stream = respond.send_response(Response::new(()), false)?; + poll_fn(|cx| send_stream.poll_reset(cx)).await?; + Ok(()) + } + + async fn send_body( + &self, + mut send_stream: SendStream, + mut body: Bytes, + ) -> Result { + let total = body.len(); + + send_stream.reserve_capacity(total); + + let mut available = send_stream.capacity(); + + while available < body.len() { + if available > 0 { + send_stream.send_data(body.split_to(available), false)?; + } + + available = match poll_fn(|cx| send_stream.poll_capacity(cx)).await { + Some(Ok(n)) => n, + Some(Err(err)) => return Err(err), + None => return Ok(total), // no longer streaming + } + } + + send_stream.send_data(body, true)?; + + Ok(total) + } +} diff --git a/recorded-response-server/src/servers.rs b/recorded-response-server/src/servers.rs new file mode 100644 index 0000000..076839f --- /dev/null +++ b/recorded-response-server/src/servers.rs @@ -0,0 +1,50 @@ +use super::Config; +use super::Server; +use futures::future::try_join_all; +use std::io; +use std::ops::Deref; +use std::sync::Arc; +use tokio_rustls::rustls; +use tracerbench_recorded_response_set::RecordedResponseSets; + +pub struct Servers(Vec); + +impl Deref for Servers { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for Servers { + fn from(config: Config) -> Self { + Self::from_config(config) + } +} + +impl Servers { + pub fn from_parts( + tls_config: Arc, + mut response_sets: RecordedResponseSets, + ) -> Self { + let mut servers: Vec = Vec::with_capacity(response_sets.len()); + for response_set in response_sets.drain(..) { + servers.push(Server::new(tls_config.clone(), response_set)); + } + Servers(servers) + } + + pub fn from_config(config: Config) -> Self { + Self::from_parts(config.tls_config, config.response_sets) + } + + pub async fn start(&self) -> Result<(), io::Error> { + let mut futures = Vec::with_capacity(self.len()); + for server in self.iter() { + futures.push(server.start()); + } + try_join_all(futures).await?; + Ok(()) + } +} diff --git a/recorded-response-set/Cargo.toml b/recorded-response-set/Cargo.toml new file mode 100644 index 0000000..bb82662 --- /dev/null +++ b/recorded-response-set/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "tracerbench-recorded-response-set" +version = "0.1.0" +authors = ["Kris Selden "] +license = "BSD-2-Clause" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bytes = "0.4" +http = "0.1" +memmap = "0.7" +serde = "1" +serde_derive = "1" +tracerbench-request-key = { path = "../request-key" } + diff --git a/recorded-response-set/src/body_table.rs b/recorded-response-set/src/body_table.rs new file mode 100644 index 0000000..f6fb7d6 --- /dev/null +++ b/recorded-response-set/src/body_table.rs @@ -0,0 +1,22 @@ +use bytes::Bytes; +use serde::Deserialize; +use serde::Deserializer; +use std::ops::Deref; + +pub(super) struct BodyTable(Vec); + +impl<'de> Deserialize<'de> for BodyTable { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(BodyTable(super::util::deserialize_bytes_seq(deserializer)?)) + } +} + +impl Deref for BodyTable { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/recorded-response-set/src/header_name_table.rs b/recorded-response-set/src/header_name_table.rs new file mode 100644 index 0000000..4cfbe6b --- /dev/null +++ b/recorded-response-set/src/header_name_table.rs @@ -0,0 +1,24 @@ +use http::header::HeaderName; +use serde::Deserialize; +use serde::Deserializer; +use std::ops::Deref; + +pub(super) struct HeaderNameTable(Vec); + +impl Deref for HeaderNameTable { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'de> Deserialize<'de> for HeaderNameTable { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(HeaderNameTable( + super::util::deserialize_str_seq_into_parsed_vec(deserializer)?, + )) + } +} diff --git a/recorded-response-set/src/header_value_table.rs b/recorded-response-set/src/header_value_table.rs new file mode 100644 index 0000000..b2bfd5e --- /dev/null +++ b/recorded-response-set/src/header_value_table.rs @@ -0,0 +1,24 @@ +use http::HeaderValue; +use serde::Deserialize; +use serde::Deserializer; +use std::ops::Deref; + +pub(super) struct HeaderValueTable(Vec); + +impl Deref for HeaderValueTable { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'de> Deserialize<'de> for HeaderValueTable { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(HeaderValueTable( + super::util::deserialize_str_seq_into_parsed_vec(deserializer)?, + )) + } +} diff --git a/recorded-response-set/src/headers_table.rs b/recorded-response-set/src/headers_table.rs new file mode 100644 index 0000000..c265d01 --- /dev/null +++ b/recorded-response-set/src/headers_table.rs @@ -0,0 +1,118 @@ +use super::util::BuilderVisitor; +use super::util::SequenceBuilder; +use super::HeaderNameTable; +use super::HeaderValueTable; +use http::HeaderMap; +use serde::de::DeserializeSeed; +use serde::de::SeqAccess; +use serde::Deserializer; +use std::ops::Deref; +use std::ops::DerefMut; +use std::sync::Arc; + +pub(super) struct HeadersTable(Vec>); + +impl Deref for HeadersTable { + type Target = Vec>; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for HeadersTable { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +pub(super) struct HeadersTableBuilder { + name_table: HeaderNameTable, + value_table: HeaderValueTable, +} + +impl HeadersTableBuilder { + pub(super) fn new(name_table: HeaderNameTable, value_table: HeaderValueTable) -> Self { + HeadersTableBuilder { + name_table, + value_table, + } + } + + pub(super) fn into_visitor(self) -> BuilderVisitor { + self.into() + } + + fn element_seed(&self) -> BuilderVisitor> { + HeaderMapBuilder::new(&self.name_table, &self.value_table).into() + } +} + +impl<'de> DeserializeSeed<'de> for HeadersTableBuilder { + type Value = HeadersTable; + + fn deserialize(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_seq(self.into_visitor()) + } +} + +impl<'de> SequenceBuilder<'de> for HeadersTableBuilder { + type Output = HeadersTable; + + fn with_size_hint(&self, hint: Option) -> HeadersTable { + HeadersTable(super::util::vec_with_size_hint(hint)) + } + + fn append(&self, output: &mut HeadersTable, mut seq: S) -> Result<(), S::Error> + where + S: SeqAccess<'de>, + { + while let Some(header_map) = seq.next_element_seed(self.element_seed())? { + output.push(Arc::new(header_map)); + } + Ok(()) + } +} + +struct HeaderMapBuilder<'a> { + name_table: &'a HeaderNameTable, + value_table: &'a HeaderValueTable, +} + +impl<'a> HeaderMapBuilder<'a> { + fn new( + name_table: &'a HeaderNameTable, + value_table: &'a HeaderValueTable, + ) -> HeaderMapBuilder<'a> { + HeaderMapBuilder { + name_table, + value_table, + } + } +} + +impl<'de> SequenceBuilder<'de> for HeaderMapBuilder<'_> { + type Output = HeaderMap; + + fn with_size_hint(&self, hint: Option) -> HeaderMap { + match hint { + Some(len) => HeaderMap::with_capacity(len), + None => HeaderMap::new(), + } + } + + fn append(&self, output: &mut HeaderMap, mut seq: S) -> Result<(), S::Error> + where + S: SeqAccess<'de>, + { + while let Some((name_index, value_index)) = seq.next_element::<(usize, usize)>()? { + output.append( + self.name_table[name_index].clone(), + self.value_table[value_index].clone(), + ); + } + Ok(()) + } +} diff --git a/recorded-response-set/src/lib.rs b/recorded-response-set/src/lib.rs new file mode 100644 index 0000000..7a46daa --- /dev/null +++ b/recorded-response-set/src/lib.rs @@ -0,0 +1,22 @@ +#![warn(rust_2018_idioms)] +#![warn(clippy::all)] + +mod body_table; +mod header_name_table; +mod header_value_table; +mod headers_table; +mod recorded_response; +mod recorded_response_set; +mod response_table; +mod util; + +use body_table::BodyTable; +use header_name_table::HeaderNameTable; +use header_value_table::HeaderValueTable; +use headers_table::HeadersTable; +use headers_table::HeadersTableBuilder; +pub use recorded_response::RecordedResponse; +pub use recorded_response_set::RecordedResponseSet; +pub use recorded_response_set::RecordedResponseSets; +use response_table::ResponseTable; +use response_table::ResponseTableBuilder; diff --git a/recorded-response-set/src/recorded_response.rs b/recorded-response-set/src/recorded_response.rs new file mode 100644 index 0000000..ce54b4c --- /dev/null +++ b/recorded-response-set/src/recorded_response.rs @@ -0,0 +1,43 @@ +use bytes::Bytes; +use http::{HeaderMap, Response, StatusCode}; +use std::sync::Arc; + +/// cheap to clone structure of response data +#[derive(Debug, Clone)] +pub struct RecordedResponse { + status_code: StatusCode, + headers: Arc, + body: Option, +} + +impl RecordedResponse { + pub fn to_parts(&self) -> (Response<()>, Option) { + let mut response = Response::new(()); + *response.status_mut() = self.status_code; + *response.headers_mut() = self.headers.as_ref().clone(); + let body = self.body.clone(); + (response, body) + } +} + +impl RecordedResponse { + pub fn new(status_code: StatusCode, headers: Arc, body: Option) -> Self { + RecordedResponse { + status_code, + headers, + body, + } + } + + pub fn status_code(&self) -> StatusCode { + self.status_code + } + + pub fn headers(&self) -> &HeaderMap { + self.headers.as_ref() + } + + pub fn body(&self) -> Option<&Bytes> { + self.body.as_ref() + } +} diff --git a/recorded-response-set/src/recorded_response_set.rs b/recorded-response-set/src/recorded_response_set.rs new file mode 100644 index 0000000..c0e009d --- /dev/null +++ b/recorded-response-set/src/recorded_response_set.rs @@ -0,0 +1,186 @@ +use super::BodyTable; +use super::HeaderNameTable; +use super::HeaderValueTable; +use super::HeadersTableBuilder; +use super::RecordedResponse; +use super::ResponseTable; +use super::ResponseTableBuilder; +use bytes::Bytes; +use http::Method; +use http::Response; +use http::Uri; +use serde::de::Error; +use serde::de::SeqAccess; +use serde::de::Visitor; +use serde::Deserialize; +use serde::Deserializer; +use std::collections::hash_map; +use std::collections::HashMap; +use std::fmt; +use std::ops::Deref; +use std::ops::DerefMut; +use std::sync::Arc; +use tracerbench_request_key::RequestKey; + +/// Represents a named set of recorded responses. +#[derive(Debug)] +pub struct RecordedResponseSet { + socks_port: u16, + name: String, + entry_key: String, + request_key: RequestKey, + response_map: HashMap, +} + +impl RecordedResponseSet { + pub fn socks_port(&self) -> u16 { + self.socks_port + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn entry_key(&self) -> &str { + &self.entry_key + } + + pub fn request_key(&self) -> &RequestKey { + &self.request_key + } + + pub fn response_for(&self, method: &Method, uri: &Uri) -> Option<(Response<()>, Option)> { + let key = self.key_for(method, uri); + let recorded_response = self.response_map.get(&key); + recorded_response.map(|recorded_response| recorded_response.to_parts()) + } + + pub fn key_for(&self, method: &Method, uri: &Uri) -> String { + let authority = match uri.authority_part() { + Some(authority) => authority.as_str(), + None => "*", // should never happen in h2 + }; + let path_and_query = match uri.path_and_query() { + Some(path_and_query) => path_and_query.as_str(), + None => "/", // should always have this + }; + self + .request_key + .key_for(method.as_str(), authority, path_and_query) + } + + pub fn requests(&self) -> hash_map::Iter<'_, String, RecordedResponse> { + self.response_map.iter() + } + + pub fn get_response(&self, key: &str) -> Option<&RecordedResponse> { + self.response_map.get(key) + } + + fn from_raw<'a>(raw_set: RawResponseSet<'a>, response_table: &ResponseTable) -> Self { + let raw_map = raw_set.request_key_map; + let mut response_map: HashMap = HashMap::with_capacity(raw_map.len()); + + for (key, index) in raw_map.iter() { + response_map.insert((*key).to_owned(), response_table[*index].clone()); + } + + RecordedResponseSet { + socks_port: raw_set.socks_port, + name: raw_set.name.to_owned(), + entry_key: raw_set.entry_key.to_owned(), + request_key: raw_set.request_key_program, + response_map, + } + } +} + +#[derive(Debug, serde_derive::Deserialize)] +#[serde(rename_all = "camelCase")] +struct RawResponseSet<'a> { + socks_port: u16, + name: &'a str, + entry_key: &'a str, + request_key_program: RequestKey, + request_key_map: HashMap<&'a str, usize>, +} + +#[derive(Debug)] +pub struct RecordedResponseSets(Vec>); + +impl<'a> Deref for RecordedResponseSets { + type Target = Vec>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> DerefMut for RecordedResponseSets { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +struct RecordedResponseSetsVisitor; + +impl<'de> Visitor<'de> for RecordedResponseSetsVisitor { + type Value = RecordedResponseSets; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("sequence of bodies, header names, header values, headers, responses, sets") + } + + fn visit_seq(self, mut seq: S) -> Result + where + S: SeqAccess<'de>, + { + let body_table = seq + .next_element::()? + .ok_or_else(|| S::Error::custom("expected 1st element to be a body table"))?; + + let name_table = seq + .next_element::()? + .ok_or_else(|| S::Error::custom("expected 2nd element to be a header name table"))?; + + let value_table = seq + .next_element::()? + .ok_or_else(|| S::Error::custom("expected 3rd element to be a header value table"))?; + + let headers_builder = HeadersTableBuilder::new(name_table, value_table); + + let headers_table = seq + .next_element_seed(headers_builder.into_visitor())? + .ok_or_else(|| S::Error::custom("expected 4th element to be a headers table"))?; + + let response_builder = ResponseTableBuilder::new(headers_table, body_table); + + let response_table = seq + .next_element_seed(response_builder.into_visitor())? + .ok_or_else(|| S::Error::custom("expected 5th element to be a response table"))?; + + let mut raw_sets: Vec> = seq + .next_element()? + .ok_or_else(|| S::Error::custom("expected 6th element to be a response set sequence"))?; + + let mut sets = Vec::with_capacity(raw_sets.len()); + + for raw_set in raw_sets.drain(..) { + sets.push(Arc::new(RecordedResponseSet::from_raw( + raw_set, + &response_table, + ))); + } + + Ok(RecordedResponseSets(sets)) + } +} + +impl<'de> Deserialize<'de> for RecordedResponseSets { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(deserializer.deserialize_seq(RecordedResponseSetsVisitor)?) + } +} diff --git a/recorded-response-set/src/response_table.rs b/recorded-response-set/src/response_table.rs new file mode 100644 index 0000000..d3a037d --- /dev/null +++ b/recorded-response-set/src/response_table.rs @@ -0,0 +1,67 @@ +use super::util::BuilderVisitor; +use super::util::SequenceBuilder; +use super::BodyTable; +use super::HeadersTable; +use super::RecordedResponse; +use http::StatusCode; +use serde::de::Error; +use serde::de::SeqAccess; +use std::ops::Deref; +use std::ops::DerefMut; + +pub(super) struct ResponseTable(Vec); + +impl Deref for ResponseTable { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for ResponseTable { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +pub(super) struct ResponseTableBuilder { + headers_table: HeadersTable, + body_table: BodyTable, +} + +impl ResponseTableBuilder { + pub(super) fn new(headers_table: HeadersTable, body_table: BodyTable) -> Self { + ResponseTableBuilder { + headers_table, + body_table, + } + } + + pub(super) fn into_visitor(self) -> BuilderVisitor { + self.into() + } +} + +impl<'de> SequenceBuilder<'de> for ResponseTableBuilder { + type Output = ResponseTable; + + fn with_size_hint(&self, hint: Option) -> ResponseTable { + ResponseTable(super::util::vec_with_size_hint(hint)) + } + + fn append(&self, output: &mut ResponseTable, mut seq: S) -> Result<(), S::Error> + where + S: SeqAccess<'de>, + { + while let Some((status, headers_index, body_index)) = + seq.next_element::<(u16, usize, Option)>()? + { + let status_code = StatusCode::from_u16(status).map_err(S::Error::custom)?; + let headers = self.headers_table[headers_index].clone(); + let body = body_index.map(|i| self.body_table[i].clone()); + + output.push(RecordedResponse::new(status_code, headers, body)); + } + Ok(()) + } +} diff --git a/recorded-response-set/src/util.rs b/recorded-response-set/src/util.rs new file mode 100644 index 0000000..0a2ba49 --- /dev/null +++ b/recorded-response-set/src/util.rs @@ -0,0 +1,173 @@ +use serde::de::DeserializeSeed; +use serde::de::Error; +use serde::de::SeqAccess; +use serde::de::Visitor; +use serde::Deserialize; +use serde::Deserializer; +use std::convert::From; +use std::fmt; +use std::fmt::Display; +use std::marker::PhantomData; +use std::str::FromStr; + +pub(super) fn deserialize_str_seq_into_parsed_vec<'de, T, D>( + deserializer: D, +) -> Result, D::Error> +where + T: FromStr, + T::Err: Display, + D: Deserializer<'de>, +{ + struct FromStrBuilder(PhantomData); + + impl FromStrBuilder { + fn new() -> FromStrBuilder { + FromStrBuilder(PhantomData) + } + } + + impl<'de, T> SequenceBuilder<'de> for FromStrBuilder + where + T: FromStr, + T::Err: Display, + { + type Output = Vec; + + fn with_size_hint(&self, hint: Option) -> Vec { + vec_with_size_hint(hint) + } + + fn append(&self, container: &mut Vec, mut seq: S) -> Result<(), S::Error> + where + S: SeqAccess<'de>, + { + while let Some(text) = seq.next_element::<&str>()? { + let header = text.parse().map_err(S::Error::custom)?; + container.push(header); + } + Ok(()) + } + } + + deserializer.deserialize_seq(BuilderVisitor::from(FromStrBuilder::new())) +} + +pub(super) fn deserialize_bytes_seq<'de, D, T>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, + T: From<&'de [u8]>, +{ + deserialize_seq_into_vec(deserializer) +} + +fn deserialize_seq_into_vec<'de, U, D, T>(deserializer: D) -> Result, D::Error> +where + U: Deserialize<'de>, + D: Deserializer<'de>, + T: From, +{ + struct FromBuilder(PhantomData T>); + + impl FromBuilder { + fn new() -> FromBuilder { + FromBuilder(PhantomData) + } + } + + impl<'de, T, E> SequenceBuilder<'de> for FromBuilder + where + T: From, + E: Deserialize<'de>, + { + type Output = Vec; + + fn with_size_hint(&self, hint: Option) -> Vec { + vec_with_size_hint(hint) + } + + fn append(&self, container: &mut Vec, mut seq: S) -> Result<(), S::Error> + where + S: SeqAccess<'de>, + { + while let Some(element) = seq.next_element::()? { + container.push(element.into()); + } + Ok(()) + } + } + + let builder = FromBuilder::new(); + deserializer.deserialize_seq(BuilderVisitor::from(builder)) +} + +pub fn vec_with_size_hint(hint: Option) -> Vec { + match hint { + Some(len) => Vec::with_capacity(len), + None => Vec::new(), + } +} + +pub(super) trait SequenceBuilder<'de> { + type Output; + fn with_size_hint(&self, size_hint: Option) -> Self::Output; + fn append(&self, output: &mut Self::Output, seq: S) -> Result<(), S::Error> + where + S: SeqAccess<'de>; +} + +pub(super) struct BuilderVisitor(B); + +impl<'de, B> BuilderVisitor +where + B: SequenceBuilder<'de>, +{ + pub(super) fn from(builder: B) -> BuilderVisitor { + BuilderVisitor(builder) + } +} + +impl<'de, B> Visitor<'de> for BuilderVisitor +where + B: SequenceBuilder<'de>, +{ + type Value = B::Output; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("sequence") + } + + fn visit_seq(self, seq: S) -> Result + where + S: SeqAccess<'de>, + { + let mut container = self.0.with_size_hint(seq.size_hint()); + self + .0 + .append(&mut container, seq) + .map_err(S::Error::custom)?; + Ok(container) + } +} + +impl<'de, B> DeserializeSeed<'de> for BuilderVisitor +where + B: SequenceBuilder<'de>, +{ + type Value = B::Output; + + fn deserialize(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_seq(self) + } +} + +impl<'de, B> From for BuilderVisitor +where + B: SequenceBuilder<'de>, +{ + fn from(builder: B) -> BuilderVisitor { + BuilderVisitor::from(builder) + } +} diff --git a/request-key/Cargo.toml b/request-key/Cargo.toml new file mode 100644 index 0000000..8ffe692 --- /dev/null +++ b/request-key/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "tracerbench-request-key" +version = "0.1.0" +authors = ["Kris Selden "] +license = "BSD-2-Clause" +edition = "2018" + +[dependencies] +regex = "1.3" +serde = "1" +serde_derive = "1" + +[dev-dependencies] +serde_cbor = "0.10" + diff --git a/request-key/src/lib.rs b/request-key/src/lib.rs new file mode 100644 index 0000000..35fa886 --- /dev/null +++ b/request-key/src/lib.rs @@ -0,0 +1,3 @@ +mod request_key; + +pub use crate::request_key::RequestKey; diff --git a/request-key/src/request_key/literal_table.rs b/request-key/src/request_key/literal_table.rs new file mode 100644 index 0000000..ed72928 --- /dev/null +++ b/request-key/src/request_key/literal_table.rs @@ -0,0 +1,52 @@ +use super::regex_replace::RegexReplace; +use super::regex_test::RegexTest; + +#[derive(serde_derive::Deserialize)] +#[serde(tag = "type", content = "content")] +pub enum Literal { + String(String), + Match(RegexTest), + Replace(RegexReplace), + ReplaceAll(RegexReplace), +} + +impl Literal { + fn as_str(&self) -> &str { + match *self { + Literal::String(ref s) => s, + _ => panic!("expected a Literal::String"), + } + } + + fn as_regex_test(&self) -> &RegexTest { + match self { + Literal::Match(regex_test) => ®ex_test, + _ => panic!("expected a Literal::Match"), + } + } + + fn as_regex_replace(&self) -> &RegexReplace { + match self { + Literal::Replace(regex_replace) => ®ex_replace, + Literal::ReplaceAll(regex_replace) => ®ex_replace, + _ => panic!("expected a Literal::Replace or Literal::ReplaceAll"), + } + } +} + +#[derive(serde_derive::Deserialize)] +pub struct LiteralTable(Vec); + +impl LiteralTable { + pub(super) fn as_str(&self, index: usize) -> &str { + self.0[index].as_str() + } + + pub(super) fn as_regex_test(&self, index: usize) -> &RegexTest { + self.0[index].as_regex_test() + } + + pub(super) fn as_regex_replace(&self, index: usize) -> &RegexReplace { + self.0[index].as_regex_replace() + } +} diff --git a/request-key/src/request_key/mod.rs b/request-key/src/request_key/mod.rs new file mode 100644 index 0000000..634e765 --- /dev/null +++ b/request-key/src/request_key/mod.rs @@ -0,0 +1,45 @@ +mod literal_table; +mod opcode; +mod program; +mod regex_replace; +mod regex_test; +mod request_parts; +mod state; + +use program::Program; +use serde::Deserialize; +use serde::Deserializer; +use state::State; +use std::fmt; + +pub struct RequestKey { + program: Program, +} + +impl fmt::Debug for RequestKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("RequestKey").field(&self.program).finish() + } +} + +impl RequestKey { + fn new(program: Program) -> RequestKey { + RequestKey { program } + } + + pub fn key_for(&self, method: &str, authority: &str, path_and_query: &str) -> String { + let mut state = State::new(method, authority, path_and_query); + self.program.exec(&mut state); + state.key() + } +} + +impl<'de> Deserialize<'de> for RequestKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let program = ::deserialize(deserializer)?; + Ok(RequestKey::new(program)) + } +} diff --git a/request-key/src/request_key/opcode.rs b/request-key/src/request_key/opcode.rs new file mode 100644 index 0000000..91a057d --- /dev/null +++ b/request-key/src/request_key/opcode.rs @@ -0,0 +1,325 @@ +use super::literal_table::LiteralTable; +use super::request_parts::RequestPart; +use super::state::State; +use serde::Deserialize; +use serde::Deserializer; +use std::fmt; +use std::ops::Deref; + +pub(super) trait Op { + fn exec<'a, 'b: 'a>(&self, table: &'b LiteralTable, state: &mut State<'a>); + fn fmt(&self, table: &LiteralTable, f: &mut fmt::Formatter<'_>) -> fmt::Result; +} + +pub(super) struct Opcodes(Vec); + +impl Deref for Opcodes { + type Target = [Opcode]; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Opcodes { + pub(super) fn exec<'a, 'b: 'a>(&self, table: &'b LiteralTable, state: &mut State<'a>) { + state.set_len(self.len()); + while state.has_next() { + self[state.next()].exec(table, state); + } + } +} + +impl<'de: 'a, 'a> Deserialize<'de> for Opcodes { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let bytes = <&'a [u8]>::deserialize(deserializer)?; + let mut opcodes: Vec = Vec::with_capacity(bytes.len() / 4); + for chunk in bytes.chunks_exact(4) { + opcodes.push(chunk.into()); + } + Ok(Opcodes(opcodes)) + } +} + +pub(super) enum Opcode { + Stop(StopOp), + JumpUnless(JumpUnlessOp), + ClearValue(ClearValueOp), + MoveStringToValue(MoveStringToValueOp), + MovePartToValue(MovePartToValueOp), + MoveValueToPart(MoveValueToPartOp), + TestValueEquals(TestValueEqualsOp), + TestValueStartsWith(TestValueStartsWithOp), + TestValueEndsWith(TestValueEndsWithOp), + TestValueIncludes(TestValueIncludesOp), + TestValueMatchesRegex(TestValueMatchesRegexOp), + ValueRegexReplace(ValueRegexReplaceOp), + ValueRegexReplaceAll(ValueRegexReplaceAllOp), +} + +impl Op for Opcode { + fn exec<'a, 'b: 'a>(&self, table: &'b LiteralTable, state: &mut State<'a>) { + match self { + Opcode::Stop(op) => op.exec(table, state), + Opcode::JumpUnless(op) => op.exec(table, state), + Opcode::ClearValue(op) => op.exec(table, state), + Opcode::MoveStringToValue(op) => op.exec(table, state), + Opcode::MovePartToValue(op) => op.exec(table, state), + Opcode::MoveValueToPart(op) => op.exec(table, state), + Opcode::TestValueEquals(op) => op.exec(table, state), + Opcode::TestValueStartsWith(op) => op.exec(table, state), + Opcode::TestValueEndsWith(op) => op.exec(table, state), + Opcode::TestValueIncludes(op) => op.exec(table, state), + Opcode::TestValueMatchesRegex(op) => op.exec(table, state), + Opcode::ValueRegexReplace(op) => op.exec(table, state), + Opcode::ValueRegexReplaceAll(op) => op.exec(table, state), + } + } + + fn fmt(&self, table: &LiteralTable, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Opcode::Stop(op) => op.fmt(table, f), + Opcode::JumpUnless(op) => op.fmt(table, f), + Opcode::ClearValue(op) => op.fmt(table, f), + Opcode::MoveStringToValue(op) => op.fmt(table, f), + Opcode::MovePartToValue(op) => op.fmt(table, f), + Opcode::MoveValueToPart(op) => op.fmt(table, f), + Opcode::TestValueEquals(op) => op.fmt(table, f), + Opcode::TestValueStartsWith(op) => op.fmt(table, f), + Opcode::TestValueEndsWith(op) => op.fmt(table, f), + Opcode::TestValueIncludes(op) => op.fmt(table, f), + Opcode::TestValueMatchesRegex(op) => op.fmt(table, f), + Opcode::ValueRegexReplace(op) => op.fmt(table, f), + Opcode::ValueRegexReplaceAll(op) => op.fmt(table, f), + } + } +} + +impl From<&[u8]> for Opcode { + fn from(chunk: &[u8]) -> Opcode { + let op = chunk[0]; + let operand = chunk[1] as usize | ((chunk[2] as usize) << 8) | ((chunk[3] as usize) << 16); + match op { + 0 => Opcode::Stop(StopOp), + 1 => Opcode::JumpUnless(JumpUnlessOp { addr: operand }), + 10 => Opcode::ClearValue(ClearValueOp), + 11 => Opcode::MoveStringToValue(MoveStringToValueOp { index: operand }), + 12 => Opcode::MovePartToValue(MovePartToValueOp { + part: operand.into(), + }), + 13 => Opcode::MoveValueToPart(MoveValueToPartOp { + part: operand.into(), + }), + 20 => Opcode::TestValueEquals(TestValueEqualsOp { index: operand }), + 21 => Opcode::TestValueStartsWith(TestValueStartsWithOp { index: operand }), + 22 => Opcode::TestValueEndsWith(TestValueEndsWithOp { index: operand }), + 23 => Opcode::TestValueIncludes(TestValueIncludesOp { index: operand }), + 24 => Opcode::TestValueMatchesRegex(TestValueMatchesRegexOp { index: operand }), + 30 => Opcode::ValueRegexReplace(ValueRegexReplaceOp { index: operand }), + 31 => Opcode::ValueRegexReplaceAll(ValueRegexReplaceAllOp { index: operand }), + _ => panic!("unknown op {}", op), + } + } +} + +pub(super) struct StopOp; + +pub(super) struct JumpUnlessOp { + addr: usize, +} + +impl JumpUnlessOp { + pub(super) fn addr_from(&self, base: usize) -> usize { + self.addr - base + } +} + +pub(super) struct ClearValueOp; + +pub(super) struct MoveStringToValueOp { + index: usize, +} + +pub(super) struct MovePartToValueOp { + part: RequestPart, +} + +pub(super) struct MoveValueToPartOp { + part: RequestPart, +} + +pub(super) struct TestValueEqualsOp { + index: usize, +} + +pub(super) struct TestValueStartsWithOp { + index: usize, +} + +pub(super) struct TestValueEndsWithOp { + index: usize, +} + +pub(super) struct TestValueIncludesOp { + index: usize, +} + +pub(super) struct TestValueMatchesRegexOp { + index: usize, +} + +pub(super) struct ValueRegexReplaceOp { + index: usize, +} + +pub(super) struct ValueRegexReplaceAllOp { + index: usize, +} + +impl Op for StopOp { + fn exec<'a, 'b: 'a>(&self, _: &'b LiteralTable, state: &mut State<'a>) { + state.stop(); + } + + fn fmt(&self, _: &LiteralTable, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Stop") + } +} + +impl Op for JumpUnlessOp { + fn exec<'a, 'b: 'a>(&self, _: &'b LiteralTable, state: &mut State<'a>) { + state.jump_unless(self.addr); + } + + fn fmt(&self, _: &LiteralTable, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("JumpUnless").field(&self.addr).finish() + } +} + +impl Op for ClearValueOp { + fn exec<'a, 'b: 'a>(&self, _: &'b LiteralTable, state: &mut State<'a>) { + state.clear_value(); + } + + fn fmt(&self, _: &LiteralTable, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("ClearValue") + } +} + +impl Op for MoveStringToValueOp { + fn exec<'a, 'b: 'a>(&self, table: &'b LiteralTable, state: &mut State<'a>) { + state.move_string_to_value(table.as_str(self.index)); + } + + fn fmt(&self, table: &LiteralTable, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("MoveStringToValue") + .field(&table.as_str(self.index)) + .finish() + } +} + +impl Op for MovePartToValueOp { + fn exec<'a, 'b: 'a>(&self, _: &'b LiteralTable, state: &mut State<'a>) { + state.move_part_to_value(self.part); + } + + fn fmt(&self, _: &LiteralTable, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("MovePartToValue").field(&self.part).finish() + } +} + +impl Op for MoveValueToPartOp { + fn exec<'a, 'b: 'a>(&self, _: &'b LiteralTable, state: &mut State<'a>) { + state.move_value_to_part(self.part); + } + + fn fmt(&self, _: &LiteralTable, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("MoveValueToPart").field(&self.part).finish() + } +} + +impl Op for TestValueEqualsOp { + fn exec<'a, 'b: 'a>(&self, table: &'b LiteralTable, state: &mut State<'a>) { + state.test_value_equals(table.as_str(self.index)); + } + + fn fmt(&self, table: &LiteralTable, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("TestValueEquals") + .field(&table.as_str(self.index)) + .finish() + } +} + +impl Op for TestValueStartsWithOp { + fn exec<'a, 'b: 'a>(&self, table: &'b LiteralTable, state: &mut State<'a>) { + state.test_value_starts_with(table.as_str(self.index)); + } + + fn fmt(&self, table: &LiteralTable, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("TestValueStartsWith") + .field(&table.as_str(self.index)) + .finish() + } +} + +impl Op for TestValueEndsWithOp { + fn exec<'a, 'b: 'a>(&self, table: &'b LiteralTable, state: &mut State<'a>) { + state.test_value_ends_with(table.as_str(self.index)); + } + + fn fmt(&self, table: &LiteralTable, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("TestValueEndsWith") + .field(&table.as_str(self.index)) + .finish() + } +} + +impl Op for TestValueIncludesOp { + fn exec<'a, 'b: 'a>(&self, table: &'b LiteralTable, state: &mut State<'a>) { + state.test_value_includes(table.as_str(self.index)); + } + + fn fmt(&self, table: &LiteralTable, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("TestValueIncludes") + .field(&table.as_str(self.index)) + .finish() + } +} + +impl Op for TestValueMatchesRegexOp { + fn exec<'a, 'b: 'a>(&self, table: &'b LiteralTable, state: &mut State<'a>) { + state.test_value_matches(table.as_regex_test(self.index)); + } + + fn fmt(&self, table: &LiteralTable, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("TestValueMatchesRegex") + .field(&table.as_regex_test(self.index)) + .finish() + } +} + +impl Op for ValueRegexReplaceOp { + fn exec<'a, 'b: 'a>(&self, table: &'b LiteralTable, state: &mut State<'a>) { + state.value_regex_replace(table.as_regex_replace(self.index)); + } + + fn fmt(&self, table: &LiteralTable, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("ValueRegexReplace") + .field(&table.as_regex_replace(self.index)) + .finish() + } +} + +impl Op for ValueRegexReplaceAllOp { + fn exec<'a, 'b: 'a>(&self, table: &'b LiteralTable, state: &mut State<'a>) { + state.value_regex_replace_all(table.as_regex_replace(self.index)); + } + + fn fmt(&self, table: &LiteralTable, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("ValueRegexReplaceAll") + .field(&table.as_regex_replace(self.index)) + .finish() + } +} diff --git a/request-key/src/request_key/program.rs b/request-key/src/request_key/program.rs new file mode 100644 index 0000000..530cbae --- /dev/null +++ b/request-key/src/request_key/program.rs @@ -0,0 +1,113 @@ +use super::literal_table::LiteralTable; +use super::opcode::Op; +use super::opcode::Opcode; +use super::opcode::Opcodes; +use super::state::State; +use std::fmt; + +#[derive(serde_derive::Deserialize)] +pub struct Program(LiteralTable, Opcodes); + +impl Program { + pub(super) fn exec<'a, 'b: 'a>(&'b self, state: &mut State<'a>) { + self.1.exec(&self.0, state); + } +} + +/// groups jumped section under JumpUnless for pretty printing the program. +/// passes literal table to opcode.fmt so it can pretty print with the +/// expanded literal. +impl fmt::Debug for Program { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + struct DebugOp<'a> { + table: &'a LiteralTable, + op: &'a Opcode, + offset: usize, + children: Option<&'a [Opcode]>, + } + + impl fmt::Debug for DebugOp<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(ops) = self.children { + let slice = DebugOpSlice { + table: self.table, + offset: self.offset, + ops, + }; + f.debug_tuple("JumpUnless").field(&slice).finish() + } else { + self.op.fmt(self.table, f) + } + } + } + + struct DebugOpSlice<'a> { + table: &'a LiteralTable, + offset: usize, + ops: &'a [Opcode], + } + + impl fmt::Debug for DebugOpSlice<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list() + .entries(DebugOpIter { + table: self.table, + ops: self.ops, + offset: self.offset, + i: 0, + }) + .finish() + } + } + + struct DebugOpIter<'a> { + table: &'a LiteralTable, + ops: &'a [Opcode], + offset: usize, + i: usize, + } + + impl<'a> Iterator for DebugOpIter<'a> { + type Item = DebugOp<'a>; + fn next(&mut self) -> Option { + let i = self.i; + if i < self.ops.len() { + let offset = self.offset; + let op = &self.ops[i]; + if let Opcode::JumpUnless(jump_unless) = op { + let start = i + 1; + let end = jump_unless.addr_from(offset); + self.i = end; + Some(DebugOp { + table: self.table, + op, + offset: offset + start, + children: Some(&self.ops[start..end]), + }) + } else { + self.i = i + 1; + Some(DebugOp { + table: self.table, + op, + offset: offset + i, + children: None, + }) + } + } else { + None + } + } + } + + let table = &self.0; + let ops = &self.1; + f.debug_list() + .entries(DebugOpIter { + table, + ops, + offset: 0, + i: 0, + }) + .finish() + } +} diff --git a/request-key/src/request_key/regex_replace.rs b/request-key/src/request_key/regex_replace.rs new file mode 100644 index 0000000..0029ab0 --- /dev/null +++ b/request-key/src/request_key/regex_replace.rs @@ -0,0 +1,197 @@ +use regex::Regex; +use serde::Deserialize; +use serde::Deserializer; +use std::borrow::Cow; +use std::fmt; + +pub struct RegexReplace(Regex, String); + +impl fmt::Debug for RegexReplace { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("").field(&self.0).field(&self.1).finish() + } +} + +impl<'a> From<(&'a str, &'a str)> for RegexReplace { + fn from(tuple: (&'a str, &'a str)) -> Self { + let (pattern, replacement_text) = tuple; + let regex = Regex::new(pattern).unwrap(); + let fixed = fix_js_replacement(replacement_text, regex.captures_len()); + RegexReplace(regex, fixed.to_string()) + } +} + +impl<'de> Deserialize<'de> for RegexReplace { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let tuple = <(&str, &str)>::deserialize(deserializer)?; + Ok(RegexReplace::from(tuple)) + } +} + +impl RegexReplace { + pub fn replace<'b>(&self, text: &'b str) -> Cow<'b, str> { + self.0.replace(text, self.1.as_str()) + } + + pub fn replace_all<'b>(&self, text: &'b str) -> Cow<'b, str> { + self.0.replace_all(text, self.1.as_str()) + } +} + +impl<'a> PartialEq for RegexReplace { + fn eq(&self, other: &RegexReplace) -> bool { + self.0.as_str() == other.0.as_str() && self.1 == other.1 + } +} + +fn fix_js_replacement(text: &str, captures_len: usize) -> Cow { + let mut start = 0; + let mut count = 0; + + for (i, b) in text.bytes().enumerate() { + if b == b'$' { + if count == 0 { + start = i + 1; + } + count += 1; + } + } + + if count == 0 { + return Cow::Borrowed(text); + } + + let mut dst = String::with_capacity(text.len() + count * 2); + + dst.push_str(&text[..start]); + let mut slice = &text[start..]; + + while !slice.is_empty() { + if slice.starts_with('$') { + dst.push('$'); + // $$ + slice = &slice[1..]; + } else { + let len = match_capture(slice, captures_len); + if len > 0 { + dst.push('{'); + dst.push_str(&slice[..len]); + dst.push('}'); + slice = &slice[len..]; + } + } + + // find next $ + if let Some(i) = slice.find('$') { + let end = i + 1; + dst.push_str(&slice[..end]); + slice = &slice[end..]; + } else { + dst.push_str(slice); + break; + } + } + Cow::Owned(dst) +} + +fn match_capture(slice: &str, m: usize) -> usize { + let mut iter = slice.chars(); + match iter.next() { + Some(n @ '1'..='9') => match iter.next() { + Some(nn @ '0'..='9') => { + if digit_val(n) * 10 + digit_val(nn) <= m { + 2 + } else { + 1 + } + } + _ => 1, + }, + Some('0') => match iter.next() { + Some(nn @ '1'..='9') => { + if digit_val(nn) <= m { + 2 + } else { + 0 + } + } + _ => 0, + }, + _ => 0, + } +} + +fn digit_val(c: char) -> usize { + c as usize - '0' as usize +} + +#[cfg(test)] +mod tests { + use super::*; + use std::borrow::Cow; + + #[test] + fn test_fix_js_replacement() { + assert_eq!( + fix_js_replacement("$12003", 3), + Cow::Borrowed("${1}2003").into_owned() + ); + assert_eq!( + fix_js_replacement("$12003", 12), + Cow::Borrowed("${12}003").into_owned() + ); + + // js is limited to 1-99 + assert_eq!( + fix_js_replacement("$12003", 120), + Cow::Borrowed("${12}003").into_owned() + ); + + assert_eq!( + fix_js_replacement("123$12003", 120), + Cow::Borrowed("123${12}003").into_owned() + ); + + assert_eq!( + fix_js_replacement("$$$12003", 120), + Cow::Borrowed("$$${12}003").into_owned() + ); + + assert_eq!( + fix_js_replacement("a$2b$1c$3", 3), + Cow::Borrowed("a${2}b${1}c${3}").into_owned() + ); + } + + #[test] + fn test_regex_replace() { + let regex = RegexReplace::from(("([^\\d])\\d{13}\\b", "$11546300800000")); + assert_eq!( + regex.replace("ts=1568844418065"), + Cow::Borrowed("ts=1546300800000").into_owned() + ); + + assert_eq!( + regex.replace("ts=1568844418065&another=1568844623195"), + Cow::Borrowed("ts=1546300800000&another=1568844623195").into_owned() + ); + } + + #[test] + fn test_regex_replace_all() { + let regex = RegexReplace::from(("([^\\d])\\d{13}\\b", "$11546300800000")); + + assert_eq!( + regex.replace_all("ts=1568844418065"), + Cow::Borrowed("ts=1546300800000").into_owned() + ); + + assert_eq!( + regex.replace_all("ts=1568844418065&another=1568844623195"), + Cow::Borrowed("ts=1546300800000&another=1546300800000").into_owned() + ); + } +} diff --git a/request-key/src/request_key/regex_test.rs b/request-key/src/request_key/regex_test.rs new file mode 100644 index 0000000..9e4ee76 --- /dev/null +++ b/request-key/src/request_key/regex_test.rs @@ -0,0 +1,54 @@ +use regex::Regex; +use serde::Deserialize; +use serde::Deserializer; +use std::fmt; + +pub struct RegexTest(Regex); + +impl RegexTest { + pub fn is_match(&self, text: &str) -> bool { + self.0.is_match(text) + } +} + +impl fmt::Debug for RegexTest { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl From<&str> for RegexTest { + fn from(pattern: &str) -> Self { + RegexTest(Regex::new(pattern).unwrap()) + } +} + +impl PartialEq for RegexTest { + fn eq(&self, other: &RegexTest) -> bool { + self.0.as_str() == other.0.as_str() + } +} + +impl<'de: 'a, 'a> Deserialize<'de> for RegexTest { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let text = <&'a str>::deserialize(deserializer)?; + Ok(RegexTest::from(text)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_regex_test() { + let regex = RegexTest::from("(one|two)"); + + assert_eq!(regex.is_match("/one/"), true); + assert_eq!(regex.is_match("/two/"), true); + assert_eq!(regex.is_match("/three/"), false); + } +} diff --git a/request-key/src/request_key/request_parts.rs b/request-key/src/request_key/request_parts.rs new file mode 100644 index 0000000..a77ca87 --- /dev/null +++ b/request-key/src/request_key/request_parts.rs @@ -0,0 +1,433 @@ +use core::convert::From; +use std::borrow::Cow; +use RequestPart::*; + +pub(super) struct RequestParts<'a> { + method: Cow<'a, str>, + authority: Cow<'a, str>, + path_and_query: Cow<'a, str>, + query_index: Option, +} + +impl<'a> RequestParts<'a> { + pub(super) fn new( + method: &'a str, + authority: &'a str, + path_and_query: &'a str, + ) -> RequestParts<'a> { + RequestParts { + method: Cow::Borrowed(method), + authority: Cow::Borrowed(authority), + path_and_query: Cow::Borrowed(path_and_query), + query_index: path_and_query.find('?'), + } + } + + pub(super) fn get_part(&self, part: RequestPart) -> Option<&str> { + match part { + Authority => Some(&self.authority), + Method => Some(&self.method), + PathAndQuery => Some(&self.path_and_query), + Path => Some(self.get_path()), + Query => self.get_query(), + } + } + + pub(super) fn set_part(&mut self, part: RequestPart, value: Option<&str>) { + match part { + Method => self.set_method(value), + Authority => self.set_authority(value), + PathAndQuery => self.set_path_and_query(value), + Path => self.set_path(value), + Query => self.set_query(value), + } + } + + /// Consume self and return string key for request. + pub(super) fn key(self) -> String { + format!("{} {} {}", self.method, self.authority, self.path_and_query) + } + + fn get_path(&self) -> &str { + if let Some(i) = self.query_index { + &self.path_and_query[..i] + } else { + &self.path_and_query + } + } + + fn get_query(&self) -> Option<&str> { + if let Some(i) = self.query_index { + Some(&self.path_and_query[i + 1..]) + } else { + None + } + } + + fn set_authority(&mut self, replacement: Option<&str>) { + let authority = replacement.unwrap_or("*"); + self.authority.to_mut().replace_range(.., authority) + } + + fn set_method(&mut self, replacement: Option<&str>) { + let method = replacement.unwrap_or("*"); + self.method.to_mut().replace_range(.., method) + } + + fn set_path_and_query(&mut self, replacement: Option<&str>) { + if let Some(path_and_query) = replacement { + self + .path_and_query + .to_mut() + .replace_range(.., path_and_query); + self.query_index = path_and_query.find('?'); + } else { + self.path_and_query = Cow::Borrowed("/"); + self.query_index = None; + } + } + + fn set_path(&mut self, replacement: Option<&str>) { + let path = replacement.unwrap_or("/"); + let path_and_query = self.path_and_query.to_mut(); + if let Some(i) = self.query_index { + path_and_query.replace_range(..i, path); + self.query_index = Some(path.len()); + } else { + path_and_query.replace_range(.., path); + } + } + + fn set_query(&mut self, replacement: Option<&str>) { + if let Some(query) = replacement { + let path_and_query = self.path_and_query.to_mut(); + if let Some(i) = self.query_index { + path_and_query.replace_range(i + 1.., query); + } else { + let i = path_and_query.len(); + path_and_query.push('?'); + path_and_query.push_str(query); + self.query_index = Some(i); + } + } else if let Some(i) = self.query_index { + self.path_and_query.to_mut().replace_range(i.., ""); + self.query_index = None; + } + } +} + +#[derive(Debug, PartialEq, Clone, Copy)] +pub(super) enum RequestPart { + Method, + Authority, + PathAndQuery, + Path, + Query, +} + +impl From for RequestPart { + fn from(part: usize) -> RequestPart { + match part { + 0 => RequestPart::Method, + 1 => RequestPart::Authority, + 2 => RequestPart::PathAndQuery, + 3 => RequestPart::Path, + 4 => RequestPart::Query, + _ => panic!("invalid RequestPart {}", part), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_path_and_query() { + let parts = RequestParts::new("POST", "www.blah.com", "/path/to/something?query=params"); + + assert_eq!(parts.get_part(RequestPart::Method), Some("POST")); + assert_eq!(parts.get_part(RequestPart::Authority), Some("www.blah.com")); + assert_eq!( + parts.get_part(RequestPart::PathAndQuery), + Some("/path/to/something?query=params") + ); + assert_eq!( + parts.get_part(RequestPart::Path), + Some("/path/to/something") + ); + assert_eq!(parts.get_part(RequestPart::Query), Some("query=params")); + + assert_eq!( + parts.key(), + "POST www.blah.com /path/to/something?query=params" + ); + } + + #[test] + fn test_path_and_query_replace_path_and_query_with_none() { + let mut parts = RequestParts::new("GET", "www.example.com", "/path/to/something?query=params"); + + parts.set_part(RequestPart::PathAndQuery, None); + + assert_eq!(parts.get_part(RequestPart::PathAndQuery), Some("/")); + assert_eq!(parts.get_part(RequestPart::Path), Some("/")); + assert_eq!(parts.get_part(RequestPart::Query), None); + + assert_eq!(parts.key(), "GET www.example.com /"); + } + + #[test] + fn test_path_and_query_replace_path_and_query_with_path_only() { + let mut parts = RequestParts::new("GET", "www.example.com", "/path/to/something?query=params"); + + parts.set_part(RequestPart::PathAndQuery, Some("/something/else")); + + assert_eq!( + parts.get_part(RequestPart::PathAndQuery), + Some("/something/else") + ); + assert_eq!(parts.get_part(RequestPart::Path), Some("/something/else")); + assert_eq!(parts.get_part(RequestPart::Query), None); + + assert_eq!(parts.key(), "GET www.example.com /something/else"); + } + + #[test] + fn test_path_and_query_replace_path_and_query_with_path_and_query() { + let mut parts = RequestParts::new("GET", "www.example.com", "/path/to/something?query=params"); + + parts.set_part( + RequestPart::PathAndQuery, + Some("/something/else?another=query"), + ); + + assert_eq!( + parts.get_part(RequestPart::PathAndQuery), + Some("/something/else?another=query") + ); + assert_eq!(parts.get_part(RequestPart::Path), Some("/something/else")); + assert_eq!(parts.get_part(RequestPart::Query), Some("another=query")); + + assert_eq!( + parts.key(), + "GET www.example.com /something/else?another=query" + ); + } + + #[test] + fn test_path_and_query_replace_path_with_none() { + let mut parts = RequestParts::new("GET", "www.example.com", "/path/to/something?query=params"); + + parts.set_part(RequestPart::Path, None); + + assert_eq!( + parts.get_part(RequestPart::PathAndQuery), + Some("/?query=params") + ); + assert_eq!(parts.get_part(RequestPart::Path), Some("/")); + assert_eq!(parts.get_part(RequestPart::Query), Some("query=params")); + + assert_eq!(parts.key(), "GET www.example.com /?query=params"); + } + + #[test] + fn test_path_and_query_replace_path_with_path() { + let mut parts = RequestParts::new("GET", "www.example.com", "/path/to/something?query=params"); + + parts.set_part(RequestPart::Path, Some("/something/else")); + + assert_eq!( + parts.get_part(RequestPart::PathAndQuery), + Some("/something/else?query=params") + ); + assert_eq!(parts.get_part(RequestPart::Path), Some("/something/else")); + assert_eq!(parts.get_part(RequestPart::Query), Some("query=params")); + + assert_eq!( + parts.key(), + "GET www.example.com /something/else?query=params" + ); + } + + #[test] + fn test_path_and_query_replace_query_with_none() { + let mut parts = RequestParts::new("GET", "www.example.com", "/path/to/something?query=params"); + + parts.set_part(RequestPart::Query, None); + + assert_eq!( + parts.get_part(RequestPart::PathAndQuery), + Some("/path/to/something") + ); + assert_eq!( + parts.get_part(RequestPart::Path), + Some("/path/to/something") + ); + assert_eq!(parts.get_part(RequestPart::Query), None); + assert_eq!(parts.key(), "GET www.example.com /path/to/something"); + } + + #[test] + fn test_path_and_query_replace_query_with_query() { + let mut parts = RequestParts::new("GET", "www.example.com", "/path/to/something?query=params"); + + parts.set_part(RequestPart::Query, Some("another=query")); + + assert_eq!( + parts.get_part(RequestPart::PathAndQuery), + Some("/path/to/something?another=query") + ); + assert_eq!( + parts.get_part(RequestPart::Path), + Some("/path/to/something") + ); + assert_eq!(parts.get_part(RequestPart::Query), Some("another=query")); + + assert_eq!( + parts.key(), + "GET www.example.com /path/to/something?another=query" + ); + } + + #[test] + fn test_path_only() { + let parts = RequestParts::new("GET", "www.example.com", "/path/to/something"); + + assert_eq!(parts.get_part(RequestPart::Method), Some("GET")); + assert_eq!( + parts.get_part(RequestPart::Authority), + Some("www.example.com") + ); + assert_eq!( + parts.get_part(RequestPart::PathAndQuery), + Some("/path/to/something") + ); + assert_eq!( + parts.get_part(RequestPart::Path), + Some("/path/to/something") + ); + assert_eq!(parts.get_part(RequestPart::Query), None); + + assert_eq!(parts.key(), "GET www.example.com /path/to/something"); + } + + #[test] + fn test_path_only_replace_path_and_query_with_none() { + let mut parts = RequestParts::new("GET", "www.example.com", "/path/to/something"); + + parts.set_part(RequestPart::PathAndQuery, None); + + assert_eq!(parts.get_part(RequestPart::PathAndQuery), Some("/")); + assert_eq!(parts.get_part(RequestPart::Path), Some("/")); + assert_eq!(parts.get_part(RequestPart::Query), None); + } + + #[test] + fn test_path_only_replace_path_and_query_with_path_only() { + let mut parts = RequestParts::new("GET", "www.example.com", "/path/to/something"); + + parts.set_part(RequestPart::PathAndQuery, Some("/something/else")); + + assert_eq!( + parts.get_part(RequestPart::PathAndQuery), + Some("/something/else") + ); + assert_eq!(parts.get_part(RequestPart::Path), Some("/something/else")); + assert_eq!(parts.get_part(RequestPart::Query), None); + + assert_eq!(parts.key(), "GET www.example.com /something/else"); + } + + #[test] + fn test_path_only_replace_path_and_query_with_path_and_query() { + let mut parts = RequestParts::new("GET", "www.example.com", "/path/to/something"); + + parts.set_part( + RequestPart::PathAndQuery, + Some("/something/else?another=query"), + ); + + assert_eq!( + parts.get_part(RequestPart::PathAndQuery), + Some("/something/else?another=query") + ); + assert_eq!(parts.get_part(RequestPart::Path), Some("/something/else")); + assert_eq!(parts.get_part(RequestPart::Query), Some("another=query")); + + assert_eq!( + parts.key(), + "GET www.example.com /something/else?another=query" + ); + } + + #[test] + fn test_path_only_replace_path_with_none() { + let mut parts = RequestParts::new("GET", "www.example.com", "/path/to/something"); + + parts.set_part(RequestPart::Path, None); + + assert_eq!(parts.get_part(RequestPart::PathAndQuery), Some("/")); + assert_eq!(parts.get_part(RequestPart::Path), Some("/")); + assert_eq!(parts.get_part(RequestPart::Query), None); + + assert_eq!(parts.key(), "GET www.example.com /"); + } + + #[test] + fn test_path_only_replace_path_with_path() { + let mut parts = RequestParts::new("GET", "www.example.com", "/path/to/something"); + + parts.set_part(RequestPart::Path, Some("/something/else")); + + assert_eq!( + parts.get_part(RequestPart::PathAndQuery), + Some("/something/else") + ); + assert_eq!(parts.get_part(RequestPart::Path), Some("/something/else")); + assert_eq!(parts.get_part(RequestPart::Query), None); + + assert_eq!(parts.key(), "GET www.example.com /something/else"); + } + + #[test] + fn test_path_only_replace_query_with_none() { + let mut parts = RequestParts::new("GET", "www.example.com", "/path/to/something"); + + parts.set_part(RequestPart::Query, None); + + assert_eq!( + parts.get_part(RequestPart::PathAndQuery), + Some("/path/to/something") + ); + assert_eq!( + parts.get_part(RequestPart::Path), + Some("/path/to/something") + ); + assert_eq!(parts.get_part(RequestPart::Query), None); + + assert_eq!(parts.key(), "GET www.example.com /path/to/something"); + } + + #[test] + fn test_path_only_replace_query_with_query() { + let mut parts = RequestParts::new("GET", "www.example.com", "/path/to/something"); + + parts.set_part(RequestPart::Query, Some("another=query")); + + assert_eq!( + parts.get_part(RequestPart::PathAndQuery), + Some("/path/to/something?another=query") + ); + assert_eq!( + parts.get_part(RequestPart::Path), + Some("/path/to/something") + ); + assert_eq!(parts.get_part(RequestPart::Query), Some("another=query")); + + assert_eq!( + parts.key(), + "GET www.example.com /path/to/something?another=query" + ); + } +} diff --git a/request-key/src/request_key/state.rs b/request-key/src/request_key/state.rs new file mode 100644 index 0000000..4a326ca --- /dev/null +++ b/request-key/src/request_key/state.rs @@ -0,0 +1,200 @@ +use super::request_parts::RequestPart; +use super::request_parts::RequestParts; +use crate::request_key::regex_replace::RegexReplace; +use crate::request_key::regex_test::RegexTest; +use std::borrow::Cow; + +pub(super) enum Value<'a> { + Part(RequestPart), + Literal(&'a str), + Mutated(String), +} + +pub(super) struct State<'a> { + request_parts: RequestParts<'a>, + ip: usize, + len: usize, + test: bool, + value: Option>, +} + +impl<'a> State<'a> { + pub(super) fn new(method: &'a str, authority: &'a str, path_and_query: &'a str) -> State<'a> { + State { + request_parts: RequestParts::new(method, authority, path_and_query), + ip: 0, + len: 0, + test: false, + value: None, + } + } + + pub(super) fn has_next(&self) -> bool { + self.ip < self.len + } + + pub(super) fn next(&mut self) -> usize { + let ip = self.ip; + self.ip = ip + 1; + ip + } + + pub(super) fn set_len(&mut self, len: usize) { + self.len = len; + } + + pub(super) fn stop(&mut self) { + self.ip = self.len; + } + + pub(super) fn jump_unless(&mut self, addr: usize) { + if !self.test { + self.ip = addr; + } + } + + pub(super) fn clear_value(&mut self) { + self.value = None; + } + + pub(super) fn move_string_to_value(&mut self, literal: &'a str) { + self.value = Some(Value::Literal(literal)); + } + + pub(super) fn move_part_to_value(&mut self, part: RequestPart) { + self.value = Some(Value::Part(part)); + } + + pub(super) fn move_value_to_part(&mut self, part: RequestPart) { + match self.value { + Some(Value::Literal(literal)) => self.request_parts.set_part(part, Some(literal)), + Some(Value::Mutated(ref text)) => (self.request_parts.set_part(part, Some(text))), + Some(Value::Part(value_part)) => { + if value_part != part { + panic!("moving a part to another part is not supported"); + } + } + None => self.request_parts.set_part(part, None), + } + self.value = None; + } + + pub(super) fn test_value_equals(&mut self, literal: &'a str) { + if let Some(value) = self.read_value() { + self.test = value == literal; + } else { + self.test = false; + } + } + + pub(super) fn test_value_starts_with(&mut self, literal: &'a str) { + if let Some(value) = self.read_value() { + self.test = value.starts_with(literal) + } else { + self.test = false; + } + } + + pub(super) fn test_value_ends_with(&mut self, literal: &'a str) { + if let Some(value) = self.read_value() { + self.test = value.ends_with(literal) + } else { + self.test = false; + } + } + + pub(super) fn test_value_includes(&mut self, literal: &'a str) { + if let Some(value) = self.read_value() { + self.test = value.contains(literal) + } else { + self.test = false; + } + } + + pub(super) fn test_value_matches(&mut self, regex: &RegexTest) { + if let Some(value) = self.read_value() { + self.test = regex.is_match(value); + } else { + self.test = false; + } + } + + pub(super) fn value_regex_replace(&mut self, regex: &RegexReplace) { + if let Some(value) = self.read_value() { + let new_value = regex.replace(value); + // if regex actually changed the value + if let Cow::Owned(value) = new_value { + self.value = Some(Value::Mutated(value)); + } + } + } + + pub(super) fn value_regex_replace_all(&mut self, regex: &RegexReplace) { + if let Some(value) = self.read_value() { + let new_value = regex.replace_all(value); + // if regex actually changed the value + if let Cow::Owned(value) = new_value { + self.value = Some(Value::Mutated(value)); + } + } + } + + fn read_value(&mut self) -> Option<&str> { + match self.value { + Some(Value::Literal(literal)) => Some(literal), + Some(Value::Mutated(ref text)) => Some(text), + Some(Value::Part(part)) => self.request_parts.get_part(part), + None => None, + } + } + + pub(super) fn key(self) -> String { + self.request_parts.key() + } +} + +#[test] +fn test_state_drop_path() { + let mut state = State::new("GET", "www.example.com", "/path/to/something?foo=bar"); + + state.clear_value(); + + state.move_value_to_part(RequestPart::Path); + + assert_eq!(state.key(), "GET www.example.com /?foo=bar"); +} + +#[test] +fn test_state_drop_query() { + let mut state = State::new("GET", "www.example.com", "/path/to/something?foo=bar"); + + state.clear_value(); + + state.move_value_to_part(RequestPart::Query); + + assert_eq!(state.key(), "GET www.example.com /path/to/something"); +} + +#[test] +fn test_state_drop_query_and_replace() { + let mut state = State::new("GET", "www.example.com", "/path/to/something?foo=bar"); + + state.clear_value(); + + state.move_value_to_part(RequestPart::Query); + + state.move_string_to_value("another=query"); + + state.move_value_to_part(RequestPart::Query); + + state.move_string_to_value("/another/path"); + + state.test_value_equals("/another/path"); + + state.move_value_to_part(RequestPart::Path); + + assert_eq!( + state.key(), + "GET www.example.com /another/path?another=query" + ); +} diff --git a/request-key/tests/common/mod.rs b/request-key/tests/common/mod.rs new file mode 100644 index 0000000..5e46b8f --- /dev/null +++ b/request-key/tests/common/mod.rs @@ -0,0 +1,248 @@ +use serde_cbor::Value; +use std::collections::BTreeMap; + +#[derive(Debug)] +pub struct ProgramBuilder { + program: (Vec, Vec), +} + +#[derive(Debug)] +pub enum TestType { + Equals, + StartsWith, + EndsWith, + Includes, + Matches, +} + +impl ProgramBuilder { + pub fn new() -> ProgramBuilder { + ProgramBuilder { + program: (Vec::new(), Vec::new()), + } + } + + pub fn to_bytes(self) -> Vec { + let literals = Value::Array(self.program.0); + let bytes = Value::Bytes(self.program.1); + let program = Value::Array(vec![literals, bytes]); + serde_cbor::to_vec(&program).unwrap() + } + + pub fn stop(&mut self) { + push(&mut self.program.1, Op::Stop, 0); + } + + pub fn if_part(&mut self, part: RequestPart, test: TestType, literal: &str, callback: F) + where + F: (FnMut(&mut ProgramBuilder) -> ()), + { + self.move_part_to_value(part); + match test { + TestType::Equals => self.test_value_equals(literal.to_owned()), + TestType::StartsWith => self.test_value_starts_with(literal.to_owned()), + TestType::EndsWith => self.test_value_ends_with(literal.to_owned()), + TestType::Includes => self.test_value_includes(literal.to_owned()), + TestType::Matches => self.test_value_matches(literal.to_owned()), + } + self.jump_unless(callback); + } + + pub fn drop_part(&mut self, part: RequestPart) { + self.clear_value(); + self.move_value_to_part(part); + } + + pub fn replace_part_with_string(&mut self, part: RequestPart, literal: &str) { + self.move_string_to_value(literal.to_owned()); + self.move_value_to_part(part); + } + + pub fn regex_replace_part( + &mut self, + part: RequestPart, + search: &str, + replacement: &str, + all: bool, + ) { + self.move_part_to_value(part); + if all { + self.value_regex_replace_all(search.to_owned(), replacement.to_owned()); + } else { + self.value_regex_replace(search.to_owned(), replacement.to_owned()); + } + self.move_value_to_part(part); + } + + pub fn jump_unless(&mut self, mut callback: F) + where + F: (FnMut(&mut ProgramBuilder) -> ()), + { + let jump = push(&mut self.program.1, Op::JumpUnless, 0); + callback(self); + resolve_jump(&mut self.program.1, jump); + } + + pub fn clear_value(&mut self) { + self.push(Op::ClearValue, 0); + } + + fn move_string_to_value(&mut self, literal: String) { + let index = self.push_string(literal); + self.push(Op::MoveStringToValue, index); + } + + fn move_part_to_value(&mut self, part: RequestPart) { + self.push(Op::MovePartToValue, part as usize); + } + + fn move_value_to_part(&mut self, part: RequestPart) { + self.push(Op::MoveValueToPart, part as usize); + } + + fn test_value_equals(&mut self, literal: String) { + let index = self.push_string(literal); + self.push(Op::TestValueEquals, index); + } + + fn test_value_starts_with(&mut self, literal: String) { + let index = self.push_string(literal); + self.push(Op::TestValueStartsWith, index); + } + + fn test_value_ends_with(&mut self, literal: String) { + let index = self.push_string(literal); + self.push(Op::TestValueEndsWith, index); + } + + fn test_value_includes(&mut self, literal: String) { + let index = self.push_string(literal); + self.push(Op::TestValueIncludes, index); + } + + fn test_value_matches(&mut self, literal: String) { + let index = self.push_match(literal); + self.push(Op::TestValueMatchesRegex, index); + } + + fn value_regex_replace(&mut self, search: String, replacement: String) { + let index = self.push_replace(search, replacement); + self.push(Op::ValueRegexReplace, index); + } + + fn value_regex_replace_all(&mut self, search: String, replacement: String) { + let index = self.push_replace_all(search, replacement); + self.push(Op::ValueRegexReplaceAll, index); + } + + fn push_string(&mut self, literal: String) -> usize { + push_literal( + &mut self.program.0, + make_literal("String", text_value(literal)), + ) + } + + fn push_match(&mut self, literal: String) -> usize { + push_literal( + &mut self.program.0, + make_literal("Match", text_value(literal)), + ) + } + + fn push_replace(&mut self, search: S, replace: S) -> usize + where + S: Into, + { + push_literal( + &mut self.program.0, + make_literal("Replace", tuple_value(search, replace)), + ) + } + + fn push_replace_all(&mut self, search: S, replace: S) -> usize + where + S: Into, + { + push_literal( + &mut self.program.0, + make_literal("ReplaceAll", tuple_value(search, replace)), + ) + } + + fn push(&mut self, op: Op, operand: usize) -> usize { + push(&mut self.program.1, op, operand) + } +} + +fn make_literal(literal_type: &'static str, content: Value) -> Value { + let mut map: BTreeMap = BTreeMap::new(); + map.insert(text_value("type"), text_value(literal_type)); + map.insert(text_value("content"), content); + Value::Map(map) +} + +fn text_value(text: S) -> Value +where + S: Into, +{ + Value::Text(text.into()) +} + +fn tuple_value(search: S, replace: S) -> Value +where + S: Into, +{ + Value::Array(vec![text_value(search), text_value(replace)]) +} + +fn push_literal(literals: &mut Vec, literal: Value) -> usize { + let i = literals.len(); + literals.push(literal); + i +} + +fn push(bytes: &mut Vec, op: Op, operand: usize) -> usize { + bytes.reserve_exact(4); + let i = bytes.len(); + bytes.push(op as u8); + // Litle endian u24 + bytes.push((operand & 0xFF) as u8); + bytes.push(((operand >> 8) & 0xFF) as u8); + bytes.push(((operand >> 16) & 0xFF) as u8); + i +} + +fn resolve_jump(bytecode: &mut Vec, jump_index: usize) { + let addr = bytecode.len() / 4; + bytecode[jump_index + 1] = (addr & 0xFF) as u8; + bytecode[jump_index + 2] = ((addr >> 8) & 0xFF) as u8; + bytecode[jump_index + 3] = ((addr >> 16) & 0xFF) as u8; +} + +#[derive(Debug, Copy, Clone)] +#[repr(u8)] +enum Op { + Stop = 0, + JumpUnless = 1, + ClearValue = 10, + MoveStringToValue = 11, + MovePartToValue = 12, + MoveValueToPart = 13, + TestValueEquals = 20, + TestValueStartsWith = 21, + TestValueEndsWith = 22, + TestValueIncludes = 23, + TestValueMatchesRegex = 24, + ValueRegexReplace = 30, + ValueRegexReplaceAll = 31, +} + +#[derive(Debug, Copy, Clone)] +#[repr(u8)] +pub enum RequestPart { + Method = 0, + Authority, + PathAndQuery, + Path, + Query, +} diff --git a/request-key/tests/integration_test.rs b/request-key/tests/integration_test.rs new file mode 100644 index 0000000..e0fea21 --- /dev/null +++ b/request-key/tests/integration_test.rs @@ -0,0 +1,141 @@ +extern crate serde_cbor; +extern crate serde_derive; +extern crate tracerbench_request_key; + +mod common; + +use common::{ProgramBuilder, RequestPart, TestType}; +use tracerbench_request_key::RequestKey; + +#[test] +fn test_empty() { + let builder = ProgramBuilder::new(); + + let bytes = builder.to_bytes(); + let request_key: RequestKey = serde_cbor::from_slice(&bytes).unwrap(); + + let key = request_key.key_for("POST", "example.com", "/path/to/something?query=2"); + + assert_eq!( + key, + String::from("POST example.com /path/to/something?query=2") + ); +} + +#[test] +fn test_match_drop_query() { + let mut builder = ProgramBuilder::new(); + builder.if_part(RequestPart::Method, TestType::Equals, "GET", |builder| { + builder.if_part( + RequestPart::Authority, + TestType::Equals, + "example.com", + |builder| { + builder.drop_part(RequestPart::Query); + builder.stop(); + }, + ); + }); + + let bytes = builder.to_bytes(); + let request_key: RequestKey = serde_cbor::from_slice(&bytes).unwrap(); + + assert_eq!( + request_key.key_for("POST", "example.com", "/path/to/something?query=2"), + String::from("POST example.com /path/to/something?query=2") + ); + + assert_eq!( + request_key.key_for("GET", "example.com", "/path/to/something?query=2"), + String::from("GET example.com /path/to/something") + ); + + assert_eq!( + request_key.key_for("GET", "foo.com", "/path/to/something?query=2"), + String::from("GET foo.com /path/to/something?query=2") + ); +} + +#[test] +fn test_match_swap_path() { + let mut builder = ProgramBuilder::new(); + builder.if_part(RequestPart::Path, TestType::StartsWith, "/one", |builder| { + builder.replace_part_with_string(RequestPart::Path, "/two"); + builder.stop(); // stop program here, next rule wont run + }); + builder.if_part(RequestPart::Path, TestType::EndsWith, "/two", |builder| { + builder.replace_part_with_string(RequestPart::Path, "/one"); + builder.stop(); + }); + + let bytes = builder.to_bytes(); + let request_key: RequestKey = serde_cbor::from_slice(&bytes).unwrap(); + + assert_eq!( + request_key.key_for("POST", "example.com", "/one/two?query=2"), + String::from("POST example.com /two?query=2") + ); + + assert_eq!( + request_key.key_for("GET", "example.com", "/three/two?query=2"), + String::from("GET example.com /one?query=2") + ); +} + +#[test] +fn test_match_and_regex_replace() { + let mut builder = ProgramBuilder::new(); + builder.if_part( + RequestPart::Path, + TestType::Matches, + "(one|two)", + |builder| { + builder.regex_replace_part( + RequestPart::PathAndQuery, + "([^\\d])\\d{13}\\b", + "$11546300800000", + true, + ); + builder.stop(); // stop program here, next rule wont run + }, + ); + + let bytes = builder.to_bytes(); + let request_key: RequestKey = serde_cbor::from_slice(&bytes).unwrap(); + + assert_eq!( + request_key.key_for("POST", "example.com", "/one?ts=1568844623195"), + String::from("POST example.com /one?ts=1546300800000") + ); + + assert_eq!( + request_key.key_for("GET", "example.com", "/1568844623195/two?query=2"), + String::from("GET example.com /1546300800000/two?query=2") + ); + + assert_eq!( + request_key.key_for( + "GET", + "example.com", + "/1568844623195/two?query=1568844623195" + ), + String::from("GET example.com /1546300800000/two?query=1546300800000") + ); +} + +#[test] +fn test_includes_and_replace_all() { + let mut builder = ProgramBuilder::new(); + builder.if_part(RequestPart::Path, TestType::Includes, "/foo/", |builder| { + builder.regex_replace_part(RequestPart::PathAndQuery, "(one).+?(two)", "$1/$2", false); + builder.stop(); // stop program here, next rule wont run + }); + + let bytes = builder.to_bytes(); + let request_key: RequestKey = serde_cbor::from_slice(&bytes).unwrap(); + + assert_eq!( + request_key.key_for("GET", "example.com", "/one/foo/two/one/and/two"), + String::from("GET example.com /one/two/one/and/two") + ); +} diff --git a/socks-proxy/Cargo.toml b/socks-proxy/Cargo.toml new file mode 100644 index 0000000..238ccbc --- /dev/null +++ b/socks-proxy/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "tracerbench-socks-proxy" +version = "0.1.0" +authors = ["Kris Selden "] +license = "BSD-2-Clause" +edition = "2018" + +[dependencies] +log = "0.4" +tokio = "=0.2.0-alpha.6" + diff --git a/socks-proxy/src/lib.rs b/socks-proxy/src/lib.rs new file mode 100644 index 0000000..4bc54e1 --- /dev/null +++ b/socks-proxy/src/lib.rs @@ -0,0 +1,229 @@ +#![warn(rust_2018_idioms)] +#![warn(clippy::all)] + +use log::debug; +use std::error; +use std::fmt; +use std::io; +use std::net; +use tokio::io::AsyncReadExt; +use tokio::io::AsyncWriteExt; + +const SOCKS_VERSION: u8 = b'\x05'; +const NO_AUTHENTICATION_REQUIRED: u8 = b'\x00'; +const NO_ACCEPTABLE_METHODS: u8 = b'\xFF'; +const COMMAND_NOT_SUPPORTED: u8 = b'\x07'; +const ADDRESS_TYPE_NOT_SUPPORTED: u8 = b'\x08'; +const CONNECT_COMMAND: u8 = b'\x01'; +const ADDRESS_TYPE_IPV4: u8 = b'\x01'; +const ADDRESS_TYPE_DOMAIN_NAME: u8 = b'\x03'; +const ADDRESS_TYPE_IPV6: u8 = b'\x04'; + +// +----+--------+ +// |VER | METHOD | +// +----+--------+ +// | 1 | 1 | +// +----+--------+ +static NO_AUTHENTICATION_REQUIRED_REPLY: [u8; 2] = [SOCKS_VERSION, NO_AUTHENTICATION_REQUIRED]; +static NO_ACCEPTABLE_METHODS_REPLY: [u8; 2] = [SOCKS_VERSION, NO_ACCEPTABLE_METHODS]; + +// +----+-----+-------+------+----------+----------+ +// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | +// +----+-----+-------+------+----------+----------+ +// | 1 | 1 | X'00' | 1 | Variable | 2 | +// +----+-----+-------+------+----------+----------+ +static CONNECT_REPLY: [u8; 10] = [SOCKS_VERSION, 0, 0, ADDRESS_TYPE_IPV4, 0, 0, 0, 0, 0, 0]; +static COMMAND_NOT_SUPPORTED_REPLY: [u8; 2] = [SOCKS_VERSION, COMMAND_NOT_SUPPORTED]; +static ADDRESS_TYPE_NOT_SUPPORTED_REPLY: [u8; 2] = [SOCKS_VERSION, ADDRESS_TYPE_NOT_SUPPORTED]; + +#[derive(Debug)] +pub enum SocksError { + InvalidSocksVersion(u8), + NoAcceptableMethods, + CommandNotSupported(u8), + AddressTypeNotSupported(u8), + IOError(io::Error), +} + +/// This only supports no authentication and just accepts +/// with a reply of success with 0.0.0.0:0 +/// +/// It is simply for redirecting all trafic to the local server +pub async fn socks5_handshake(mut socket: S) -> Result +where + S: AsyncReadExt + AsyncWriteExt + Unpin, +{ + // max possible socks message + // 4 header 1 domain length 255 domain name 2 port + let mut buffer: [u8; 262] = [0; 262]; + + // +----+----------+----------+ + // |VER | NMETHODS | METHODS | + // +----+----------+----------+ + // | 1 | 1 | 1 to 255 | + // +----+----------+----------+ + let mut len = read_at_least_one(&mut socket, &mut buffer).await?; + let version = buffer[0]; + if version != SOCKS_VERSION { + return Err(SocksError::InvalidSocksVersion(version)); + } + + if len < 2 { + len += read_at_least_one(&mut socket, &mut buffer[len..]).await?; + } + + let methods_len = buffer[1] as usize; + + let end: usize = methods_len + 2; + while len < end { + len += read_at_least_one(&mut socket, &mut buffer[len..]).await?; + } + + let methods = &buffer[2..end]; + let no_auth_required = methods.iter().any(|&b| b == NO_AUTHENTICATION_REQUIRED); + + if no_auth_required { + socket.write_all(&NO_AUTHENTICATION_REQUIRED_REPLY).await?; + } else { + socket.write_all(&NO_ACCEPTABLE_METHODS_REPLY).await?; + return Err(SocksError::NoAcceptableMethods); + } + + // +----+-----+-------+------+----------+----------+ + // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | + // +----+-----+-------+------+----------+----------+ + // | 1 | 1 | X'00' | 1 | Variable | 2 | + // +----+-----+-------+------+----------+----------+ + // reset buffer position + len = read_at_least_one(&mut socket, &mut buffer).await?; + + let version = buffer[0]; + if version != SOCKS_VERSION { + return Err(SocksError::InvalidSocksVersion(version)); + } + + if len < 2 { + len += read_at_least_one(&mut socket, &mut buffer[len..]).await?; + } + + let command = buffer[1]; + if command != CONNECT_COMMAND { + socket.write_all(&COMMAND_NOT_SUPPORTED_REPLY).await?; + return Err(SocksError::CommandNotSupported(command)); + } + + while len < 4 { + len += read_at_least_one(&mut socket, &mut buffer[len..]).await?; + } + + let address_type = buffer[3]; + + // now we have read enough to know the message len + let end = match address_type { + ADDRESS_TYPE_IPV4 => 4 + 4 + 2, + ADDRESS_TYPE_DOMAIN_NAME => { + if len < 5 { + len += read_at_least_one(&mut socket, &mut buffer[len..]).await?; + } + let domain_name_len = buffer[4] as usize; + 4 + 1 + domain_name_len + 2 + } + ADDRESS_TYPE_IPV6 => 4 + 16 + 2, + _ => { + socket.write_all(&ADDRESS_TYPE_NOT_SUPPORTED_REPLY).await?; + return Err(SocksError::AddressTypeNotSupported(address_type)); + } + }; + + while len < end { + len += read_at_least_one(&mut socket, &mut buffer[len..]).await?; + } + + debug!( + "CONNECT {}", + match address_type { + ADDRESS_TYPE_IPV4 => format!( + "{}", + net::SocketAddrV4::new( + net::Ipv4Addr::new(buffer[4], buffer[5], buffer[6], buffer[7]), + u16::from_be_bytes([buffer[8], buffer[9]]), + ) + ), + ADDRESS_TYPE_DOMAIN_NAME => format!( + "{}:{}", + String::from_utf8_lossy(&buffer[5..end - 2]), + u16::from_be_bytes([buffer[end - 2], buffer[end - 1]]) + ), + ADDRESS_TYPE_IPV6 => format!( + "{}", + net::SocketAddrV6::new( + net::Ipv6Addr::from([ + buffer[4], buffer[5], buffer[6], buffer[7], buffer[8], buffer[9], buffer[10], + buffer[11], buffer[12], buffer[13], buffer[14], buffer[15], buffer[16], buffer[17], + buffer[18], buffer[19], + ]), + u16::from_be_bytes([buffer[20], buffer[21]]), + 0, + 0 + ) + ), + _ => String::default(), + } + ); + + // we are capturing all traffic so we don't care about what we + // read just as long as we read it all + socket.write_all(&CONNECT_REPLY).await?; + + Ok(socket) +} + +async fn read_at_least_one(socket: &mut S, buffer: &mut [u8]) -> Result +where + S: AsyncReadExt + Unpin, +{ + let len = socket.read(buffer).await?; + if len == 0 { + return Err(io::Error::from(io::ErrorKind::UnexpectedEof)); + } + Ok(len) +} + +impl error::Error for SocksError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + SocksError::IOError(ref err) => error::Error::source(err), + _ => None, + } + } +} + +impl fmt::Display for SocksError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + SocksError::InvalidSocksVersion(version) => f.write_fmt(format_args!( + "Invalid socks version, expected 5 but got {}", + version + )), + SocksError::IOError(ref err) => f.write_fmt(format_args!( + "an IO error occurred during the socks 5 handshake: {}", + err + )), + SocksError::NoAcceptableMethods => { + f.write_str("no acceptable socks auth methods, only no authentication is supported") + } + SocksError::CommandNotSupported(ref cmd) => { + f.write_fmt(format_args!("unsupported socks command {}", cmd)) + } + SocksError::AddressTypeNotSupported(ref atype) => { + f.write_fmt(format_args!("unsupported address type {}", atype)) + } + } + } +} + +impl From for SocksError { + fn from(err: io::Error) -> SocksError { + SocksError::IOError(err) + } +} diff --git a/tracerbench-serve/Cargo.toml b/tracerbench-serve/Cargo.toml new file mode 100644 index 0000000..260bbc2 --- /dev/null +++ b/tracerbench-serve/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "tracerbench-serve" +version = "0.1.0" +authors = ["Kris Selden "] +license = "BSD-2-Clause" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +pretty_env_logger = "" +structopt = "0.3" +tokio = "=0.2.0-alpha.6" +tracerbench-recorded-response-server = { path = "../recorded-response-server" } + diff --git a/tracerbench-serve/src/main.rs b/tracerbench-serve/src/main.rs new file mode 100644 index 0000000..e74c3d7 --- /dev/null +++ b/tracerbench-serve/src/main.rs @@ -0,0 +1,32 @@ +#![warn(rust_2018_idioms)] +#![warn(clippy::all)] + +use std::path::PathBuf; +use structopt::StructOpt; +use tracerbench_recorded_response_server::Config; +use tracerbench_recorded_response_server::Servers; + +#[derive(StructOpt)] +pub struct Opt { + #[structopt(parse(from_os_str))] + pub cert: PathBuf, + #[structopt(parse(from_os_str))] + pub key: PathBuf, + #[structopt(parse(from_os_str))] + pub sets: PathBuf, +} + +#[tokio::main] +async fn main() -> Result<(), std::io::Error> { + let opt = Opt::from_args(); + + pretty_env_logger::init_timed(); + + let config = Config::from_args(&opt.cert, &opt.key, &opt.sets)?; + + let servers: Servers = config.into(); + + servers.start().await?; + + Ok(()) +}