Bug 1581637 - Part 7 - Add new vendored crates required by neqo and neqo_glue. r=heycam
authorDragana Damjanovic <dd.mozilla@gmail.com>
Fri, 01 Nov 2019 14:55:56 +0000
changeset 500214 2801613e5d2db4565cb35cbcfe6549e49e85dbd6
parent 500213 d4a5badae723ccecaa7500546c42415f6dd76797
child 500215 0bf045553a4079b9948e1814e5cd1038be301b0b
push id114164
push useraiakab@mozilla.com
push dateTue, 05 Nov 2019 10:06:15 +0000
treeherdermozilla-inbound@4d585c7edc76 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam
bugs1581637
milestone72.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1581637 - Part 7 - Add new vendored crates required by neqo and neqo_glue. r=heycam this is automatically generated by 'mach vendor rust'. Differential Revision: https://phabricator.services.mozilla.com/D47227
.cargo/config.in
Cargo.lock
third_party/rust/lazy_static/.cargo-checksum.json
third_party/rust/lazy_static/Cargo.toml
third_party/rust/lazy_static/README.md
third_party/rust/lazy_static/src/inline_lazy.rs
third_party/rust/lazy_static/src/lib.rs
third_party/rust/lazy_static/tests/test.rs
third_party/rust/neqo-common/.cargo-checksum.json
third_party/rust/neqo-common/Cargo.toml
third_party/rust/neqo-common/src/codec.rs
third_party/rust/neqo-common/src/datagram.rs
third_party/rust/neqo-common/src/incrdecoder.rs
third_party/rust/neqo-common/src/lib.rs
third_party/rust/neqo-common/src/log.rs
third_party/rust/neqo-common/src/once.rs
third_party/rust/neqo-common/src/timer.rs
third_party/rust/neqo-common/tests/log.rs
third_party/rust/neqo-crypto/.cargo-checksum.json
third_party/rust/neqo-crypto/Cargo.toml
third_party/rust/neqo-crypto/TODO
third_party/rust/neqo-crypto/bindings/bindings.toml
third_party/rust/neqo-crypto/bindings/mozpkix.hpp
third_party/rust/neqo-crypto/bindings/nspr_err.h
third_party/rust/neqo-crypto/bindings/nspr_error.h
third_party/rust/neqo-crypto/bindings/nspr_io.h
third_party/rust/neqo-crypto/bindings/nspr_time.h
third_party/rust/neqo-crypto/bindings/nss_ciphers.h
third_party/rust/neqo-crypto/bindings/nss_init.h
third_party/rust/neqo-crypto/bindings/nss_p11.h
third_party/rust/neqo-crypto/bindings/nss_secerr.h
third_party/rust/neqo-crypto/bindings/nss_ssl.h
third_party/rust/neqo-crypto/bindings/nss_sslerr.h
third_party/rust/neqo-crypto/bindings/nss_sslopt.h
third_party/rust/neqo-crypto/build.rs
third_party/rust/neqo-crypto/src/aead.rs
third_party/rust/neqo-crypto/src/agent.rs
third_party/rust/neqo-crypto/src/agentio.rs
third_party/rust/neqo-crypto/src/auth.rs
third_party/rust/neqo-crypto/src/cert.rs
third_party/rust/neqo-crypto/src/constants.rs
third_party/rust/neqo-crypto/src/err.rs
third_party/rust/neqo-crypto/src/exp.rs
third_party/rust/neqo-crypto/src/ext.rs
third_party/rust/neqo-crypto/src/hkdf.rs
third_party/rust/neqo-crypto/src/hp.rs
third_party/rust/neqo-crypto/src/lib.rs
third_party/rust/neqo-crypto/src/p11.rs
third_party/rust/neqo-crypto/src/prio.rs
third_party/rust/neqo-crypto/src/replay.rs
third_party/rust/neqo-crypto/src/result.rs
third_party/rust/neqo-crypto/src/secrets.rs
third_party/rust/neqo-crypto/src/selfencrypt.rs
third_party/rust/neqo-crypto/src/ssl.rs
third_party/rust/neqo-crypto/src/time.rs
third_party/rust/neqo-crypto/tests/aead.rs
third_party/rust/neqo-crypto/tests/agent.rs
third_party/rust/neqo-crypto/tests/ext.rs
third_party/rust/neqo-crypto/tests/handshake.rs
third_party/rust/neqo-crypto/tests/hkdf.rs
third_party/rust/neqo-crypto/tests/hp.rs
third_party/rust/neqo-crypto/tests/init.rs
third_party/rust/neqo-crypto/tests/selfencrypt.rs
third_party/rust/neqo-http3/.cargo-checksum.json
third_party/rust/neqo-http3/Cargo.toml
third_party/rust/neqo-http3/src/client_events.rs
third_party/rust/neqo-http3/src/connection.rs
third_party/rust/neqo-http3/src/connection_client.rs
third_party/rust/neqo-http3/src/connection_server.rs
third_party/rust/neqo-http3/src/control_stream_local.rs
third_party/rust/neqo-http3/src/control_stream_remote.rs
third_party/rust/neqo-http3/src/hframe.rs
third_party/rust/neqo-http3/src/lib.rs
third_party/rust/neqo-http3/src/server_events.rs
third_party/rust/neqo-http3/src/stream_type_reader.rs
third_party/rust/neqo-http3/src/transaction_client.rs
third_party/rust/neqo-http3/src/transaction_server.rs
third_party/rust/neqo-http3/tests/httpconn.rs
third_party/rust/neqo-qpack/.cargo-checksum.json
third_party/rust/neqo-qpack/Cargo.toml
third_party/rust/neqo-qpack/src/decoder.rs
third_party/rust/neqo-qpack/src/encoder.rs
third_party/rust/neqo-qpack/src/huffman.rs
third_party/rust/neqo-qpack/src/huffman_decode_helper.rs
third_party/rust/neqo-qpack/src/huffman_table.rs
third_party/rust/neqo-qpack/src/lib.rs
third_party/rust/neqo-qpack/src/qpack_helper.rs
third_party/rust/neqo-qpack/src/qpack_send_buf.rs
third_party/rust/neqo-qpack/src/static_table.rs
third_party/rust/neqo-qpack/src/table.rs
third_party/rust/neqo-transport/.cargo-checksum.json
third_party/rust/neqo-transport/Cargo.toml
third_party/rust/neqo-transport/TODO
third_party/rust/neqo-transport/src/connection.rs
third_party/rust/neqo-transport/src/crypto.rs
third_party/rust/neqo-transport/src/dump.rs
third_party/rust/neqo-transport/src/events.rs
third_party/rust/neqo-transport/src/flow_mgr.rs
third_party/rust/neqo-transport/src/frame.rs
third_party/rust/neqo-transport/src/lib.rs
third_party/rust/neqo-transport/src/packet.rs
third_party/rust/neqo-transport/src/recovery.rs
third_party/rust/neqo-transport/src/recv_stream.rs
third_party/rust/neqo-transport/src/send_stream.rs
third_party/rust/neqo-transport/src/server.rs
third_party/rust/neqo-transport/src/stats.rs
third_party/rust/neqo-transport/src/stream_id.rs
third_party/rust/neqo-transport/src/tparams.rs
third_party/rust/neqo-transport/src/tracking.rs
third_party/rust/neqo-transport/tests/conn_vectors.rs
third_party/rust/neqo-transport/tests/connection.rs
third_party/rust/neqo-transport/tests/server.rs
third_party/rust/slice-deque/.cargo-checksum.json
third_party/rust/slice-deque/Cargo.toml
third_party/rust/slice-deque/benches/alloc.rs
third_party/rust/slice-deque/benches/get.rs
third_party/rust/slice-deque/benches/index.rs
third_party/rust/slice-deque/benches/insert.rs
third_party/rust/slice-deque/benches/iter.rs
third_party/rust/slice-deque/benches/pop_back.rs
third_party/rust/slice-deque/benches/pop_front.rs
third_party/rust/slice-deque/benches/push_back.rs
third_party/rust/slice-deque/benches/push_front.rs
third_party/rust/slice-deque/benches/remove.rs
third_party/rust/slice-deque/bors.toml
third_party/rust/slice-deque/ci/deploy_and_run_on_ios_simulator.rs
third_party/rust/slice-deque/ci/run.sh
third_party/rust/slice-deque/ci/run_san.sh
third_party/rust/slice-deque/license-apache.md
third_party/rust/slice-deque/license-mit.md
third_party/rust/slice-deque/license.md
third_party/rust/slice-deque/readme.md
third_party/rust/slice-deque/rustfmt.toml
third_party/rust/slice-deque/src/lib.rs
third_party/rust/slice-deque/src/macros.rs
third_party/rust/slice-deque/src/mirrored/buffer.rs
third_party/rust/slice-deque/src/mirrored/linux.rs
third_party/rust/slice-deque/src/mirrored/macos.rs
third_party/rust/slice-deque/src/mirrored/mod.rs
third_party/rust/slice-deque/src/mirrored/sysv.rs
third_party/rust/slice-deque/src/mirrored/winapi.rs
--- a/.cargo/config.in
+++ b/.cargo/config.in
@@ -1,12 +1,17 @@
 # This file contains vendoring instructions for cargo.
 # It was generated by `mach vendor rust`.
 # Please do not edit.
 
+[source."https://github.com/mozilla/neqo"]
+branch = "master"
+git = "https://github.com/mozilla/neqo"
+replace-with = "vendored-sources"
+
 [source."https://github.com/hsivonen/packed_simd"]
 branch = "rust_1_32"
 git = "https://github.com/hsivonen/packed_simd"
 replace-with = "vendored-sources"
 
 [source."https://github.com/alexcrichton/mio-named-pipes"]
 branch = "master"
 git = "https://github.com/alexcrichton/mio-named-pipes"
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,16 +1,16 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
 [[package]]
 name = "Inflector"
 version = "0.11.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "adler32"
 version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
@@ -243,17 +243,17 @@ dependencies = [
 name = "bindgen"
 version = "0.51.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cexpr 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "clang-sys 0.28.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "peeking_take_while 0.1.2 (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)",
  "regex 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -304,17 +304,17 @@ dependencies = [
 name = "bits_client"
 version = "0.2.0"
 dependencies = [
  "bits 0.2.0",
  "comedy 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "guid_win 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "tempfile 3.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "bitsdownload"
 version = "0.1.0"
@@ -673,17 +673,17 @@ dependencies = [
 [[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.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.2.0 (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"
@@ -692,17 +692,17 @@ dependencies = [
 ]
 
 [[package]]
 name = "crossbeam-utils"
 version = "0.6.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.2.0 (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 = "cssparser"
 version = "0.25.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -777,17 +777,17 @@ dependencies = [
 name = "cubeb-coreaudio"
 version = "0.1.0"
 dependencies = [
  "atomic 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "coreaudio-sys-utils 0.1.0",
  "cubeb-backend 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.2.0 (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.60 (registry+https://github.com/rust-lang/crates.io-index)",
  "mach 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "cubeb-pulse"
 version = "0.3.0"
 dependencies = [
@@ -943,17 +943,17 @@ dependencies = [
  "rustc-demangle 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "dwrote"
 version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "lazy_static 1.2.0 (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.60 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "either"
@@ -1143,17 +1143,17 @@ dependencies = [
 [[package]]
 name = "geckodriver"
 version = "0.26.0"
 dependencies = [
  "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper 0.12.19 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.2.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.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "marionette 0.1.0",
  "mozdevice 0.1.0",
  "mozprofile 0.6.0",
  "mozrunner 0.10.0",
  "mozversion 0.2.1",
  "regex 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1238,16 +1238,17 @@ dependencies = [
  "gkrust_utils 0.1.0",
  "jsrust_shared 0.1.0",
  "kvstore 0.1.0",
  "lmdb-rkv-sys 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "mdns_service 0.1.0",
  "mozurl 0.0.1",
  "mp4parse_capi 0.11.2",
+ "neqo_glue 0.1.0",
  "netwerk_helper 0.0.1",
  "nserror 0.1.0",
  "nsstring 0.1.0",
  "prefs_parser 0.0.1",
  "profiler_helper 0.1.0",
  "rsdparsa_capi 0.1.0",
  "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "shift_or_euc_c 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1482,17 +1483,17 @@ source = "registry+https://github.com/ru
 [[package]]
 name = "js"
 version = "0.1.4"
 dependencies = [
  "bindgen 0.51.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "cmake 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
  "env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.2.0 (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.60 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "mozjs_sys 0.0.0",
  "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "jsrust"
@@ -1527,33 +1528,33 @@ source = "registry+https://github.com/ru
 
 [[package]]
 name = "kvstore"
 version = "0.1.0"
 dependencies = [
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.2.0 (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.60 (registry+https://github.com/rust-lang/crates.io-index)",
  "lmdb-rkv 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "moz_task 0.1.0",
  "nserror 0.1.0",
  "nsstring 0.1.0",
  "rkv 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "storage_variant 0.1.0",
  "tempfile 3.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "thin-vec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "xpcom 0.1.0",
 ]
 
 [[package]]
 name = "lazy_static"
-version = "1.2.0"
+version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "lazycell"
 version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -1570,17 +1571,17 @@ dependencies = [
 ]
 
 [[package]]
 name = "libloading"
 version = "0.4.3"
 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)",
- "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "libloading"
 version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
@@ -1596,17 +1597,17 @@ dependencies = [
  "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)",
  "libudev-sys 0.1.3",
 ]
 
 [[package]]
 name = "libudev-sys"
 version = "0.1.3"
 dependencies = [
- "lazy_static 1.2.0 (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.60 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "libz-sys"
 version = "1.0.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
@@ -1975,16 +1976,94 @@ dependencies = [
 ]
 
 [[package]]
 name = "murmurhash3"
 version = "0.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
+name = "neqo-common"
+version = "0.1.4"
+source = "git+https://github.com/mozilla/neqo#a3230a21d75066a117bab10a38d9a831c3d707d5"
+dependencies = [
+ "env_logger 0.6.2 (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.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "neqo-crypto"
+version = "0.1.4"
+source = "git+https://github.com/mozilla/neqo#a3230a21d75066a117bab10a38d9a831c3d707d5"
+dependencies = [
+ "bindgen 0.51.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "neqo-common 0.1.4 (git+https://github.com/mozilla/neqo)",
+ "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
+ "toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "neqo-http3"
+version = "0.1.4"
+source = "git+https://github.com/mozilla/neqo#a3230a21d75066a117bab10a38d9a831c3d707d5"
+dependencies = [
+ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "neqo-common 0.1.4 (git+https://github.com/mozilla/neqo)",
+ "neqo-crypto 0.1.4 (git+https://github.com/mozilla/neqo)",
+ "neqo-qpack 0.1.4 (git+https://github.com/mozilla/neqo)",
+ "neqo-transport 0.1.4 (git+https://github.com/mozilla/neqo)",
+ "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "neqo-qpack"
+version = "0.1.4"
+source = "git+https://github.com/mozilla/neqo#a3230a21d75066a117bab10a38d9a831c3d707d5"
+dependencies = [
+ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "neqo-common 0.1.4 (git+https://github.com/mozilla/neqo)",
+ "neqo-crypto 0.1.4 (git+https://github.com/mozilla/neqo)",
+ "neqo-transport 0.1.4 (git+https://github.com/mozilla/neqo)",
+]
+
+[[package]]
+name = "neqo-transport"
+version = "0.1.4"
+source = "git+https://github.com/mozilla/neqo#a3230a21d75066a117bab10a38d9a831c3d707d5"
+dependencies = [
+ "derive_more 0.13.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.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "neqo-common 0.1.4 (git+https://github.com/mozilla/neqo)",
+ "neqo-crypto 0.1.4 (git+https://github.com/mozilla/neqo)",
+ "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "slice-deque 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "neqo_glue"
+version = "0.1.0"
+dependencies = [
+ "neqo-common 0.1.4 (git+https://github.com/mozilla/neqo)",
+ "neqo-crypto 0.1.4 (git+https://github.com/mozilla/neqo)",
+ "neqo-http3 0.1.4 (git+https://github.com/mozilla/neqo)",
+ "neqo-transport 0.1.4 (git+https://github.com/mozilla/neqo)",
+ "nserror 0.1.0",
+ "nsstring 0.1.0",
+ "thin-vec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "xpcom 0.1.0",
+]
+
+[[package]]
 name = "net2"
 version = "0.2.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -2131,17 +2210,17 @@ dependencies = [
 
 [[package]]
 name = "osclientcerts-static"
 version = "0.1.4"
 dependencies = [
  "bindgen 0.51.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.2.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.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "pkcs11 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "owning_ref"
@@ -2504,17 +2583,17 @@ dependencies = [
 [[package]]
 name = "rayon-core"
 version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "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.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.2.0 (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.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "rdrand"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
@@ -2583,17 +2662,17 @@ name = "rkv"
 version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "lmdb-rkv 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "ordered-float 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -2901,16 +2980,26 @@ name = "size_of_test"
 version = "0.0.1"
 
 [[package]]
 name = "slab"
 version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
+name = "slice-deque"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mach 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
 name = "smallbitvec"
 version = "2.3.0"
 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"
@@ -2983,17 +3072,17 @@ dependencies = [
  "derive_more 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "fallible 0.0.1",
  "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "hashglobe 0.1.0",
  "indexmap 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.2.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.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "malloc_size_of 0.0.1",
  "malloc_size_of_derive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "new_debug_unreachable 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "nsstring 0.1.0",
  "num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3040,17 +3129,17 @@ dependencies = [
 [[package]]
 name = "style_traits"
 version = "0.0.1"
 dependencies = [
  "app_units 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.25.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "malloc_size_of 0.0.1",
  "malloc_size_of_derive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "selectors 0.21.0",
  "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_arc 0.1.1",
  "to_shmem 0.0.1",
  "to_shmem_derive 0.0.1",
 ]
@@ -3196,17 +3285,17 @@ dependencies = [
  "libc 0.2.60 (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.2.0 (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 = "thread_profiler"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -3614,17 +3703,17 @@ dependencies = [
  "core-text 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cstr 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "freetype 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)",
  "image 0.22.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.2.0 (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.60 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "malloc_size_of_derive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "plane-split 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "ron 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3820,30 +3909,30 @@ dependencies = [
  "nsstring 0.1.0",
  "xpcom 0.1.0",
 ]
 
 [[package]]
 name = "xpcom_macros"
 version = "0.1.0"
 dependencies = [
- "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "xulstore"
 version = "0.1.0"
 dependencies = [
  "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "cstr 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.2.0 (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.60 (registry+https://github.com/rust-lang/crates.io-index)",
  "lmdb-rkv 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "moz_task 0.1.0",
  "nserror 0.1.0",
  "nsstring 0.1.0",
  "rkv 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3994,17 +4083,17 @@ dependencies = [
 "checksum image 0.22.1 (registry+https://github.com/rust-lang/crates.io-index)" = "663a975007e0b49903e2e8ac0db2c432c465855f2d65f17883ba1476e85f0b42"
 "checksum indexmap 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a4d6d89e0948bf10c08b9ecc8ac5b83f07f857ebe2c0cbe38de15b4e4f510356"
 "checksum inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff"
 "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
 "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358"
 "checksum itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c069bbec61e1ca5a596166e55dfe4773ff745c3d16b700013bcaff9a6df2c682"
 "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
 "checksum khronos_api 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
-"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1"
+"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f"
 "checksum libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "d44e80633f007889c7eff624b709ab43c92d708caad982295768a7b13ca3b5eb"
 "checksum libdbus-sys 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "18cb88963258d00f4962205dbb5933d82780d9962c8c8a064b651d2ad7189210"
 "checksum libloading 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fd38073de8f7965d0c17d30546d4bb6da311ab428d1c7a3fc71dff7f9d4979b9"
 "checksum libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3ad660d7cb8c5822cd83d10897b0f1f1526792737a179e73896152f85b88c2"
 "checksum libudev 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea626d3bdf40a1c5aee3bcd4f40826970cae8d80a8fec934c82a63840094dcfe"
 "checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe"
 "checksum line-wrap 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9"
@@ -4028,16 +4117,21 @@ dependencies = [
 "checksum mio-named-pipes 0.1.6 (git+https://github.com/alexcrichton/mio-named-pipes)" = "<none>"
 "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 miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226"
 "checksum moz_cbor 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20c82a57087fd5990d7122dbff1607c3b20c3d2958e9d9ad9765aab415e2c91c"
 "checksum mp4parse_fallible 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6626c2aef76eb8f984eef02e475883d3fe9112e114720446c5810fc5f045cd30"
 "checksum msdos_time 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aad9dfe950c057b1bfe9c1f2aa51583a8468ef2a5baba2ebbe06d775efeb7729"
 "checksum murmurhash3 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a2983372caf4480544083767bf2d27defafe32af49ab4df3a0b7fc90793a3664"
+"checksum neqo-common 0.1.4 (git+https://github.com/mozilla/neqo)" = "<none>"
+"checksum neqo-crypto 0.1.4 (git+https://github.com/mozilla/neqo)" = "<none>"
+"checksum neqo-http3 0.1.4 (git+https://github.com/mozilla/neqo)" = "<none>"
+"checksum neqo-qpack 0.1.4 (git+https://github.com/mozilla/neqo)" = "<none>"
+"checksum neqo-transport 0.1.4 (git+https://github.com/mozilla/neqo)" = "<none>"
 "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
 "checksum new_debug_unreachable 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0cdc457076c78ab54d5e0d6fa7c47981757f1e34dc39ff92787f217dede586c4"
 "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2"
 "checksum nom 4.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9c349f68f25f596b9f44cf0e7c69752a5c633b0550c3ff849518bfba0233774a"
 "checksum num-bigint 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1"
 "checksum num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0c8b15b261814f992e33760b1fca9fe8b693d8a65299f20c9901688636cfb746"
 "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea"
 "checksum num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "af3fdbbc3291a5464dc57b03860ec37ca6bf915ed6ee385e7c6c052c422b2124"
@@ -4123,16 +4217,17 @@ dependencies = [
 "checksum serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)" = "38b08a9a90e5260fe01c6480ec7c811606df6d3a660415808c3c3fa8ed95b582"
 "checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68"
 "checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d"
 "checksum shift_or_euc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f930dea4685b9803954b9d74cdc175c6d946a22f2eafe5aa2e9a58cdcae7da8c"
 "checksum shift_or_euc_c 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c81ec08c8a68c45c48d8ef58b80ce038cc9945891c4a4996761e2ec5cba05abc"
 "checksum shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
 "checksum siphasher 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2ffc669b726f2bc9a3bcff66e5e23b56ba6bf70e22a34c3d7b6d0b3450b65b84"
 "checksum slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5f9776d6b986f77b35c6cf846c11ad986ff128fe0b2b63a3628e3755e8d3102d"
+"checksum slice-deque 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "31ef6ee280cdefba6d2d0b4b78a84a1c1a3f3a4cec98c2d4231c8bc225de0f25"
 "checksum smallbitvec 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1764fe2b30ee783bfe3b9b37b2649d8d590b3148bb12e0079715d4d5c673562e"
 "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7"
 "checksum socket2 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)" = "df028e0e632c2a1823d920ad74895e7f9128e6438cbc4bc6fd1f180e644767b9"
 "checksum stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "15132e0e364248108c5e2c02e3ab539be8d6f5d52a01ca9bbf27ed657316f02b"
 "checksum string 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00caf261d6f90f588f8450b8e1230fa0d5be49ee6140fdfbcb55335aff350970"
 "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
 "checksum svg_fmt 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c666f0fed8e1e20e057af770af9077d72f3d5a33157b8537c1475dd8ffd6d32b"
 "checksum syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)" = "66c8865bf5a7cbb662d8b011950060b3c8743dca141b054bf7195b20d314d8e2"
--- a/third_party/rust/lazy_static/.cargo-checksum.json
+++ b/third_party/rust/lazy_static/.cargo-checksum.json
@@ -1,1 +1,1 @@
-{"files":{"Cargo.toml":"9501e790a183ddbc845edba899bffa05f573c43fa5f79ef065ead842d056fa85","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"0621878e61f0d0fda054bcbe02df75192c28bde1ecc8289cbd86aeba2dd72720","README.md":"f2e94da8b77ac2a097dbc4b93b35912ef41b725624512d83ea968951dd5bdc7d","src/core_lazy.rs":"6b9fb6a4f553058e240756125b6b9ca43a83ed1fb72964343038ea0ea2e1af10","src/inline_lazy.rs":"2ae9a04c5bff40e80194f65b01012f0b42efa527bf717e176c68b4ca5212043c","src/lib.rs":"3defd7a82feced71862161a3c36fcff7cef3e08a51596b2e15d629b9a171a75a","tests/no_std.rs":"d68b149ee51ef5ae2b2906c0c94f5a9812d3b02811b13365c5a35e2ef90d25cf","tests/test.rs":"8e809c0f0332a3a60fca0113128cdab2cdbee92f03db523cdc4e82f4cd4b9f22"},"package":"a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1"}
\ No newline at end of file
+{"files":{"Cargo.toml":"05e37a4e63dc4a495998bb5133252a51d671c4e99061a6342089ed6eab43978a","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"0621878e61f0d0fda054bcbe02df75192c28bde1ecc8289cbd86aeba2dd72720","README.md":"e2effacb5bbd7c01523f9a9e4a6a59c0f9b8698753b210fec5742408498197df","src/core_lazy.rs":"6b9fb6a4f553058e240756125b6b9ca43a83ed1fb72964343038ea0ea2e1af10","src/inline_lazy.rs":"f6184afbca4b477616f270790edc180263be806aa92ef0a9de681b4aac9e88c4","src/lib.rs":"99096a5d3089c0d86646f0805d1455befe2cb09683704af29c5c9d99ecab2683","tests/no_std.rs":"d68b149ee51ef5ae2b2906c0c94f5a9812d3b02811b13365c5a35e2ef90d25cf","tests/test.rs":"b3f7d805375dc5af7a2aa4b869944ad2ab4fc982b35ad718ea58f6914dc0a698"},"package":"e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"}
\ No newline at end of file
--- a/third_party/rust/lazy_static/Cargo.toml
+++ b/third_party/rust/lazy_static/Cargo.toml
@@ -1,40 +1,39 @@
 # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
 #
 # When uploading crates to the registry Cargo will automatically
 # "normalize" Cargo.toml files for maximal compatibility
 # with all versions of Cargo and also rewrite `path` dependencies
-# to registry (e.g. crates.io) dependencies
+# to registry (e.g., crates.io) dependencies
 #
 # If you believe there's an error in this file please file an
 # issue against the rust-lang/cargo repository. If you're
 # editing this file be aware that the upstream Cargo.toml
 # will likely look very different (and much more reasonable)
 
 [package]
 name = "lazy_static"
-version = "1.2.0"
+version = "1.4.0"
 authors = ["Marvin Löbel <loebel.marvin@gmail.com>"]
 exclude = ["/.travis.yml", "/appveyor.yml"]
 description = "A macro for declaring lazily evaluated statics in Rust."
 documentation = "https://docs.rs/lazy_static"
 readme = "README.md"
 keywords = ["macro", "lazy", "static"]
 categories = ["no-std", "rust-patterns", "memory-management"]
 license = "MIT/Apache-2.0"
 repository = "https://github.com/rust-lang-nursery/lazy-static.rs"
 [dependencies.spin]
-version = "0.4.10"
-features = ["once"]
+version = "0.5.0"
 optional = true
-default-features = false
+[dev-dependencies.doc-comment]
+version = "0.3.1"
 
 [features]
-nightly = []
 spin_no_std = ["spin"]
 [badges.appveyor]
 repository = "rust-lang-nursery/lazy-static.rs"
 
 [badges.is-it-maintained-issue-resolution]
 repository = "rust-lang-nursery/lazy-static.rs"
 
 [badges.is-it-maintained-open-issues]
--- a/third_party/rust/lazy_static/README.md
+++ b/third_party/rust/lazy_static/README.md
@@ -3,40 +3,40 @@ lazy-static.rs
 
 A macro for declaring lazily evaluated statics in Rust.
 
 Using this macro, it is possible to have `static`s that require code to be
 executed at runtime in order to be initialized.
 This includes anything requiring heap allocations, like vectors or hash maps,
 as well as anything that requires non-const function calls to be computed.
 
-[![Travis-CI Status](https://travis-ci.org/rust-lang-nursery/lazy-static.rs.svg?branch=master)](https://travis-ci.org/rust-lang-nursery/lazy-static.rs)
+[![Travis-CI Status](https://travis-ci.com/rust-lang-nursery/lazy-static.rs.svg?branch=master)](https://travis-ci.com/rust-lang-nursery/lazy-static.rs)
 [![Latest version](https://img.shields.io/crates/v/lazy_static.svg)](https://crates.io/crates/lazy_static)
 [![Documentation](https://docs.rs/lazy_static/badge.svg)](https://docs.rs/lazy_static)
 [![License](https://img.shields.io/crates/l/lazy_static.svg)](https://github.com/rust-lang-nursery/lazy-static.rs#license)
 
 ## Minimum supported `rustc`
 
-`1.24.1+`
+`1.27.2+`
 
 This version is explicitly tested in CI and may only be bumped in new minor versions. Any changes to the supported minimum version will be called out in the release notes.
 
 
 # Getting Started
 
 [lazy-static.rs is available on crates.io](https://crates.io/crates/lazy_static).
 It is recommended to look there for the newest released version, as well as links to the newest builds of the docs.
 
 At the point of the last update of this README, the latest published version could be used like this:
 
 Add the following dependency to your Cargo manifest...
 
 ```toml
 [dependencies]
-lazy_static = "1.2.0"
+lazy_static = "1.4.0"
 ```
 
 ...and see the [docs](https://docs.rs/lazy_static) for how to use it.
 
 # Example
 
 ```rust
 #[macro_use]
--- a/third_party/rust/lazy_static/src/inline_lazy.rs
+++ b/third_party/rust/lazy_static/src/inline_lazy.rs
@@ -5,36 +5,39 @@
 // http://opensource.org/licenses/MIT>, at your option. This file may not be
 // copied, modified, or distributed except according to those terms.
 
 extern crate core;
 extern crate std;
 
 use self::std::prelude::v1::*;
 use self::std::cell::Cell;
+use self::std::hint::unreachable_unchecked;
 use self::std::sync::Once;
+#[allow(deprecated)]
 pub use self::std::sync::ONCE_INIT;
 
-// FIXME: Replace Option<T> with MaybeInitialized<T>
+// FIXME: Replace Option<T> with MaybeUninit<T> (stable since 1.36.0)
 pub struct Lazy<T: Sync>(Cell<Option<T>>, Once);
 
 impl<T: Sync> Lazy<T> {
+    #[allow(deprecated)]
     pub const INIT: Self = Lazy(Cell::new(None), ONCE_INIT);
 
     #[inline(always)]
     pub fn get<F>(&'static self, f: F) -> &T
     where
         F: FnOnce() -> T,
     {
         self.1.call_once(|| {
             self.0.set(Some(f()));
         });
 
         // `self.0` is guaranteed to be `Some` by this point
-        // The `Once` will catch and propegate panics
+        // The `Once` will catch and propagate panics
         unsafe {
             match *self.0.as_ptr() {
                 Some(ref x) => x,
                 None => {
                     debug_assert!(false, "attempted to derefence an uninitialized lazy static. This is a bug");
 
                     unreachable_unchecked()
                 },
@@ -47,19 +50,8 @@ unsafe impl<T: Sync> Sync for Lazy<T> {}
 
 #[macro_export]
 #[doc(hidden)]
 macro_rules! __lazy_static_create {
     ($NAME:ident, $T:ty) => {
         static $NAME: $crate::lazy::Lazy<$T> = $crate::lazy::Lazy::INIT;
     };
 }
-
-/// Polyfill for std::hint::unreachable_unchecked. There currently exists a
-/// [crate](https://docs.rs/unreachable) for an equivalent to std::hint::unreachable_unchecked, but
-/// lazy_static currently doesn't include any runtime dependencies and we've chosen to include this
-/// short polyfill rather than include a new crate in every consumer's build.
-///
-/// This should be replaced by std's version when lazy_static starts to require at least Rust 1.27.
-unsafe fn unreachable_unchecked() -> ! {
-    enum Void {}
-    match std::mem::uninitialized::<Void>() {}
-}
--- a/third_party/rust/lazy_static/src/lib.rs
+++ b/third_party/rust/lazy_static/src/lib.rs
@@ -85,34 +85,37 @@ fn main() {
 ```
 
 # Implementation details
 
 The `Deref` implementation uses a hidden static variable that is guarded by an atomic check on each access.
 
 # Cargo features
 
-This crate provides two cargo features:
+This crate provides one cargo feature:
 
-- `nightly`: This uses unstable language features only available on the nightly release channel for a more optimal implementation. In practice this currently means avoiding a heap allocation per static. This feature might get deprecated at a later point once all relevant optimizations are usable from stable.
-- `spin_no_std` (implies `nightly`): This allows using this crate in a no-std environment, by depending on the standalone `spin` crate.
-
-Both features depend on unstable language features, which means
-no guarantees can be made about them in regard to SemVer stability.
+- `spin_no_std`: This allows using this crate in a no-std environment, by depending on the standalone `spin` crate.
 
 */
 
-#![doc(html_root_url = "https://docs.rs/lazy_static/1.2.0")]
+#![doc(html_root_url = "https://docs.rs/lazy_static/1.4.0")]
 #![no_std]
 
 #[cfg(not(feature = "spin_no_std"))]
 #[path="inline_lazy.rs"]
 #[doc(hidden)]
 pub mod lazy;
 
+#[cfg(test)]
+#[macro_use]
+extern crate doc_comment;
+
+#[cfg(test)]
+doctest!("../README.md");
+
 #[cfg(feature = "spin_no_std")]
 #[path="core_lazy.rs"]
 #[doc(hidden)]
 pub mod lazy;
 
 #[doc(hidden)]
 pub use core::ops::Deref as __Deref;
 
@@ -191,17 +194,17 @@ pub trait LazyStatic {
 ///
 /// Example:
 ///
 /// ```rust
 /// #[macro_use]
 /// extern crate lazy_static;
 ///
 /// lazy_static! {
-///     static ref BUFFER: Vec<u8> = (0..65537).collect();
+///     static ref BUFFER: Vec<u8> = (0..255).collect();
 /// }
 ///
 /// fn main() {
 ///     lazy_static::initialize(&BUFFER);
 ///
 ///     // ...
 ///     work_with_initialized_data(&BUFFER);
 /// }
--- a/third_party/rust/lazy_static/tests/test.rs
+++ b/third_party/rust/lazy_static/tests/test.rs
@@ -128,19 +128,21 @@ lazy_static! {
 }
 
 #[test]
 fn item_name_shadowing() {
     assert_eq!(*ITEM_NAME_TEST, X);
 }
 
 use std::sync::atomic::AtomicBool;
+#[allow(deprecated)]
 use std::sync::atomic::ATOMIC_BOOL_INIT;
 use std::sync::atomic::Ordering::SeqCst;
 
+#[allow(deprecated)]
 static PRE_INIT_FLAG: AtomicBool = ATOMIC_BOOL_INIT;
 
 lazy_static! {
     static ref PRE_INIT: () = {
         PRE_INIT_FLAG.store(true, SeqCst);
         ()
     };
 }
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-common/.cargo-checksum.json
@@ -0,0 +1,1 @@
+{"files":{"Cargo.toml":"7bd3c66fe77c2dc970451bf4a41ba5347632941e4efdbd19066bf6eefcb31397","src/codec.rs":"6c619c42d1293297fd1e1763213033a82723bfa97938f5dd9fde6745f965940e","src/datagram.rs":"47d69797b66108cec997375818cb43ba9575b89f8730277047c6889de09b12aa","src/incrdecoder.rs":"4d55c9d992a4c409e4b4e37a8c04d3741d2ba260f46d5385cb3eff5121db03de","src/lib.rs":"7204f3eb32908563ffd50e615534326509be781cb5afd111f3bccdd6cf04249c","src/log.rs":"68a0a30344edcfad6c222eed62f5810fb9aa896fea7ec782b6ca2b2fc9a0bd4b","src/once.rs":"ad0d1ac0233dda75e294b5ccab65caceaec66d277659e22b1236aceea0c53ede","src/timer.rs":"13fb2ad4ef435d57895c61c291aca82a261c93c0f2cae2634929fb6ca5fdac85","tests/log.rs":"79e01eeef039d1abb17aadb2212256ad064c53e6d72bbebe254230119a623510"},"package":null}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-common/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "neqo-common"
+version = "0.1.4"
+authors = ["Bobby Holley <bobbyholley@gmail.com>"]
+edition = "2018"
+
+[dependencies]
+num-traits = "0.2"
+log = "0.4.0"
+env_logger = "0.6.1"
+lazy_static = "1.3.0"
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-common/src/codec.rs
@@ -0,0 +1,691 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::convert::TryFrom;
+use std::fmt::Debug;
+use std::ops::{Deref, DerefMut};
+
+use crate::hex;
+
+/// Decoder is a view into a byte array that has a read offset.  Use it for parsing.
+pub struct Decoder<'a> {
+    buf: &'a [u8],
+    offset: usize,
+}
+
+impl<'a> Decoder<'a> {
+    /// Make a new view of the provided slice.
+    pub fn new(buf: &[u8]) -> Decoder {
+        Decoder { buf, offset: 0 }
+    }
+
+    /// Get the number of bytes remaining until the end.
+    pub fn remaining(&self) -> usize {
+        self.buf.len() - self.offset
+    }
+
+    /// Skip n bytes.  Panics if `n` is too large.
+    pub fn skip(&mut self, n: usize) {
+        assert!(self.remaining() >= n);
+        self.offset += n;
+    }
+
+    /// Skip helper that panics if `n` is `None` or not able to fit in `usize`.
+    fn skip_inner(&mut self, n: Option<u64>) {
+        self.skip(usize::try_from(n.unwrap()).unwrap());
+    }
+
+    /// Skip a vector.  Panics if there isn't enough space.
+    /// Only use this for tests because we panic rather than reporting a result.
+    pub fn skip_vec(&mut self, n: usize) {
+        let len = self.decode_uint(n);
+        self.skip_inner(len);
+    }
+
+    /// Skip a variable length vector.  Panics if there isn't enough space.
+    /// Only use this for tests because we panic rather than reporting a result.
+    pub fn skip_vvec(&mut self) {
+        let len = self.decode_varint();
+        self.skip_inner(len);
+    }
+
+    /// Decodes (reads) a single byte.
+    pub fn decode_byte(&mut self) -> Option<u8> {
+        if self.remaining() < 1 {
+            return None;
+        }
+        let b = self.buf[self.offset];
+        self.offset += 1;
+        Some(b)
+    }
+
+    /// Provides the next byte without moving the read position.
+    pub fn peek_byte(&mut self) -> Option<u8> {
+        if self.remaining() < 1 {
+            None
+        } else {
+            Some(self.buf[self.offset])
+        }
+    }
+
+    /// Decodes arbitrary data.
+    pub fn decode(&mut self, n: usize) -> Option<&[u8]> {
+        if self.remaining() < n {
+            return None;
+        }
+        let res = &self.buf[self.offset..self.offset + n];
+        self.offset += n;
+        Some(res)
+    }
+
+    /// Decodes an unsigned integer of length 1..8.
+    pub fn decode_uint(&mut self, n: usize) -> Option<u64> {
+        assert!(n > 0 && n <= 8);
+        if self.remaining() < n {
+            return None;
+        }
+        let mut v = 0_u64;
+        for i in 0..n {
+            let b = self.buf[self.offset + i];
+            v = v << 8 | u64::from(b);
+        }
+        self.offset += n;
+        Some(v)
+    }
+
+    /// Decodes a QUIC varint.
+    pub fn decode_varint(&mut self) -> Option<u64> {
+        let b1 = match self.decode_byte() {
+            Some(b) => b,
+            _ => return None,
+        };
+        match b1 >> 6 {
+            0 => Some(u64::from(b1 & 0x3f)),
+            1 => Some((u64::from(b1 & 0x3f) << 8) | self.decode_uint(1)?),
+            2 => Some((u64::from(b1 & 0x3f) << 24) | self.decode_uint(3)?),
+            3 => Some((u64::from(b1 & 0x3f) << 56) | self.decode_uint(7)?),
+            _ => unreachable!(),
+        }
+    }
+
+    /// Decodes the rest of the buffer.  Infallible.
+    pub fn decode_remainder(&mut self) -> &[u8] {
+        let res = &self.buf[self.offset..];
+        self.offset = self.buf.len();
+        res
+    }
+
+    fn decode_checked(&mut self, n: Option<u64>) -> Option<&[u8]> {
+        let len = match n {
+            Some(l) => l,
+            _ => return None,
+        };
+        if let Ok(l) = usize::try_from(len) {
+            self.decode(l)
+        } else {
+            // sizeof(usize) < sizeof(u64) and the value is greater than
+            // usize can hold. Throw away the rest of the input.
+            self.offset = self.buf.len();
+            None
+        }
+    }
+
+    /// Decodes a TLS-style length-prefixed buffer.
+    pub fn decode_vec(&mut self, n: usize) -> Option<&[u8]> {
+        let len = self.decode_uint(n);
+        self.decode_checked(len)
+    }
+
+    /// Decodes a QUIC varint-length-prefixed buffer.
+    pub fn decode_vvec(&mut self) -> Option<&[u8]> {
+        let len = self.decode_varint();
+        self.decode_checked(len)
+    }
+}
+
+// Implement `Deref` for `Decoder` so that values can be examined without moving the cursor.
+impl<'a> Deref for Decoder<'a> {
+    type Target = [u8];
+    fn deref(&self) -> &[u8] {
+        &self.buf[self.offset..]
+    }
+}
+
+impl<'a> Debug for Decoder<'a> {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        f.write_str(&hex(self))
+    }
+}
+
+impl<'a> From<&'a [u8]> for Decoder<'a> {
+    fn from(buf: &'a [u8]) -> Decoder<'a> {
+        Decoder::new(buf)
+    }
+}
+
+impl<'a, 'b> PartialEq<Decoder<'b>> for Decoder<'a> {
+    fn eq(&self, other: &Decoder<'b>) -> bool {
+        self.buf == other.buf
+    }
+}
+
+/// Encoder is good for building data structures.
+#[derive(Clone, Default, PartialEq)]
+pub struct Encoder {
+    buf: Vec<u8>,
+}
+
+impl Encoder {
+    /// Static helper function for previewing the results of encoding without doing it.
+    pub fn varint_len(v: u64) -> usize {
+        match () {
+            _ if v < (1 << 6) => 1,
+            _ if v < (1 << 14) => 2,
+            _ if v < (1 << 30) => 4,
+            _ if v < (1 << 62) => 8,
+            _ => panic!("Varint value too large"),
+        }
+    }
+
+    /// Default construction of an empty buffer.
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// Construction of a buffer with a predetermined capacity.
+    pub fn with_capacity(capacity: usize) -> Self {
+        Self {
+            buf: Vec::with_capacity(capacity),
+        }
+    }
+
+    /// Create a view of the current contents of the buffer.
+    /// Note: for a view of a slice, use `Decoder::new(&enc[s..e])
+    pub fn as_decoder(&self) -> Decoder {
+        Decoder::new(&self)
+    }
+
+    /// Don't use this except in testing.
+    pub fn from_hex(s: &str) -> Self {
+        if s.len() % 2 != 0 {
+            panic!("Needs to be even length");
+        }
+
+        let cap = s.len() / 2;
+        let mut enc = Self::with_capacity(cap);
+
+        for i in 0..cap {
+            let v = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16).unwrap();
+            enc.encode_byte(v);
+        }
+        enc
+    }
+
+    /// Generic encode routine for arbitrary data.
+    pub fn encode(&mut self, data: &[u8]) -> &mut Self {
+        self.buf.extend_from_slice(data);
+        self
+    }
+
+    /// Encode a single byte.
+    pub fn encode_byte(&mut self, data: u8) -> &mut Self {
+        self.buf.push(data);
+        self
+    }
+
+    /// Encode an integer of any size up to u64.
+    #[allow(clippy::cast_possible_truncation)]
+    pub fn encode_uint<T: Into<u64>>(&mut self, n: usize, v: T) -> &mut Self {
+        let v = v.into();
+        assert!(n > 0 && n <= 8);
+        for i in 0..n {
+            self.encode_byte(((v >> (8 * (n - i - 1))) & 0xff) as u8);
+        }
+        self
+    }
+
+    /// Encode a QUIC varint.
+    pub fn encode_varint<T: Into<u64>>(&mut self, v: T) -> &mut Self {
+        let v = v.into();
+        match () {
+            _ if v < (1 << 6) => self.encode_uint(1, v),
+            _ if v < (1 << 14) => self.encode_uint(2, v | (1 << 14)),
+            _ if v < (1 << 30) => self.encode_uint(4, v | (2 << 30)),
+            _ if v < (1 << 62) => self.encode_uint(8, v | (3 << 62)),
+            _ => panic!("Varint value too large"),
+        };
+        self
+    }
+
+    /// Encode a vector in TLS style.
+    pub fn encode_vec(&mut self, n: usize, v: &[u8]) -> &mut Self {
+        self.encode_uint(n, u64::try_from(v.len()).unwrap())
+            .encode(v)
+    }
+
+    /// Encode a vector in TLS style using a closure for the contents.
+    #[allow(clippy::cast_possible_truncation)]
+    pub fn encode_vec_with<F: FnOnce(&mut Self)>(&mut self, n: usize, f: F) -> &mut Self {
+        let start = self.buf.len();
+        self.buf.resize(self.buf.len() + n, 0);
+        f(self);
+        let len = self.buf.len() - start - n;
+        for i in 0..n {
+            self.buf[start + i] = ((len >> (8 * (n - i - 1))) & 0xff) as u8
+        }
+        self
+    }
+
+    /// Encode a vector with a varint length.
+    pub fn encode_vvec(&mut self, v: &[u8]) -> &mut Self {
+        self.encode_varint(u64::try_from(v.len()).unwrap())
+            .encode(v)
+    }
+
+    /// Encode a vector with a varint length using a closure.
+    #[allow(clippy::cast_possible_truncation)]
+    pub fn encode_vvec_with<F: FnOnce(&mut Self)>(&mut self, f: F) -> &mut Self {
+        let start = self.buf.len();
+        // Optimize for short buffers, reserve a single byte for the length.
+        self.buf.resize(self.buf.len() + 1, 0);
+        f(self);
+        let len = self.buf.len() - start - 1;
+
+        // Now to insert a varint for `len` before the encoded block.
+        //
+        // We now have one zero byte at `start`, followed by `len` encoded bytes:
+        //   |  0  | ... encoded ... |
+        // We are going to encode a varint by putting the low bytes in that spare byte.
+        // Any additional bytes for the varint are put after the encoded blob:
+        //   | low | ... encoded ... | varint high |
+        // Then we will rotate that entire piece right, by however many bytes we add:
+        //   | varint high | low | ... encoded ... |
+        // As long as encoding more than 63 bytes is rare, this won't cost much relative
+        // to the convenience of being able to use this function.
+
+        let v = u64::try_from(len).expect("encoded value fits in a u64");
+        // The lower order byte fits before the inserted block of bytes.
+        self.buf[start] = (v & 0xff) as u8;
+        let (count, bits) = match () {
+            // Great.  The byte we have is enough.
+            _ if v < (1 << 6) => return self,
+            _ if v < (1 << 14) => (1, 1 << 6),
+            _ if v < (1 << 30) => (3, 2 << 22),
+            _ if v < (1 << 62) => (7, 3 << 54),
+            _ => panic!("Varint value too large"),
+        };
+        // Now, we need to encode the high bits after the main block, ...
+        self.encode_uint(count, (v >> 8) | bits);
+        // ..., then rotate the entire thing right by the same amount.
+        self.buf[start..].rotate_right(count);
+        self
+    }
+}
+
+impl Debug for Encoder {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        f.write_str(&hex(self))
+    }
+}
+
+impl<'a> From<Decoder<'a>> for Encoder {
+    fn from(dec: Decoder<'a>) -> Self {
+        Self::from(&dec.buf[dec.offset..])
+    }
+}
+
+impl From<&[u8]> for Encoder {
+    fn from(buf: &[u8]) -> Self {
+        Self {
+            buf: Vec::from(buf),
+        }
+    }
+}
+
+impl Into<Vec<u8>> for Encoder {
+    fn into(self) -> Vec<u8> {
+        self.buf
+    }
+}
+
+impl Deref for Encoder {
+    type Target = [u8];
+    fn deref(&self) -> &[u8] {
+        &self.buf[..]
+    }
+}
+
+impl DerefMut for Encoder {
+    fn deref_mut(&mut self) -> &mut [u8] {
+        &mut self.buf[..]
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn decode() {
+        let enc = Encoder::from_hex("012345");
+        let mut dec = enc.as_decoder();
+        assert_eq!(dec.decode(2).unwrap(), &[0x01, 0x23]);
+        assert!(dec.decode(2).is_none());
+    }
+
+    #[test]
+    fn decode_byte() {
+        let enc = Encoder::from_hex("0123");
+        let mut dec = enc.as_decoder();
+
+        assert_eq!(dec.decode_byte().unwrap(), 0x01);
+        assert_eq!(dec.decode_byte().unwrap(), 0x23);
+        assert!(dec.decode_byte().is_none());
+    }
+
+    #[test]
+    fn decode_byte_short() {
+        let enc = Encoder::from_hex("");
+        let mut dec = enc.as_decoder();
+        assert!(dec.decode_byte().is_none());
+    }
+
+    #[test]
+    fn decode_remainder() {
+        let enc = Encoder::from_hex("012345");
+        let mut dec = enc.as_decoder();
+        assert_eq!(dec.decode_remainder(), &[0x01, 0x23, 0x45]);
+        assert!(dec.decode(2).is_none());
+
+        let mut dec = Decoder::from(&enc[0..0]);
+        assert_eq!(dec.decode_remainder().len(), 0);
+    }
+
+    #[test]
+    fn decode_vec() {
+        let enc = Encoder::from_hex("012345");
+        let mut dec = enc.as_decoder();
+        assert_eq!(dec.decode_vec(1).expect("read one octet length"), &[0x23]);
+        assert_eq!(dec.remaining(), 1);
+
+        let enc = Encoder::from_hex("00012345");
+        let mut dec = enc.as_decoder();
+        assert_eq!(dec.decode_vec(2).expect("read two octet length"), &[0x23]);
+        assert_eq!(dec.remaining(), 1);
+    }
+
+    #[test]
+    fn decode_vec_short() {
+        // The length is too short.
+        let enc = Encoder::from_hex("02");
+        let mut dec = enc.as_decoder();
+        assert!(dec.decode_vec(2).is_none());
+
+        // The body is too short.
+        let enc = Encoder::from_hex("0200");
+        let mut dec = enc.as_decoder();
+        assert!(dec.decode_vec(1).is_none());
+    }
+
+    #[test]
+    fn decode_vvec() {
+        let enc = Encoder::from_hex("012345");
+        let mut dec = enc.as_decoder();
+        assert_eq!(dec.decode_vvec().expect("read one octet length"), &[0x23]);
+        assert_eq!(dec.remaining(), 1);
+
+        let enc = Encoder::from_hex("40012345");
+        let mut dec = enc.as_decoder();
+        assert_eq!(dec.decode_vvec().expect("read two octet length"), &[0x23]);
+        assert_eq!(dec.remaining(), 1);
+    }
+
+    #[test]
+    fn decode_vvec_short() {
+        // The length field is too short.
+        let enc = Encoder::from_hex("ff");
+        let mut dec = enc.as_decoder();
+        assert!(dec.decode_vvec().is_none());
+
+        let enc = Encoder::from_hex("405500");
+        let mut dec = enc.as_decoder();
+        assert!(dec.decode_vvec().is_none());
+    }
+
+    #[test]
+    fn skip() {
+        let enc = Encoder::from_hex("ffff");
+        let mut dec = enc.as_decoder();
+        dec.skip(1);
+        assert_eq!(dec.remaining(), 1);
+    }
+
+    #[test]
+    #[should_panic]
+    fn skip_too_much() {
+        let enc = Encoder::from_hex("ff");
+        let mut dec = enc.as_decoder();
+        dec.skip(2);
+    }
+
+    #[test]
+    fn skip_vec() {
+        let enc = Encoder::from_hex("012345");
+        let mut dec = enc.as_decoder();
+        dec.skip_vec(1);
+        assert_eq!(dec.remaining(), 1);
+    }
+
+    #[test]
+    #[should_panic]
+    fn skip_vec_too_much() {
+        let enc = Encoder::from_hex("ff1234");
+        let mut dec = enc.as_decoder();
+        dec.skip_vec(1);
+    }
+
+    #[test]
+    #[should_panic]
+    fn skip_vec_short_length() {
+        let enc = Encoder::from_hex("ff");
+        let mut dec = enc.as_decoder();
+        dec.skip_vec(4);
+    }
+    #[test]
+    fn skip_vvec() {
+        let enc = Encoder::from_hex("012345");
+        let mut dec = enc.as_decoder();
+        dec.skip_vvec();
+        assert_eq!(dec.remaining(), 1);
+    }
+
+    #[test]
+    #[should_panic]
+    fn skip_vvec_too_much() {
+        let enc = Encoder::from_hex("0f1234");
+        let mut dec = enc.as_decoder();
+        dec.skip_vvec();
+    }
+
+    #[test]
+    #[should_panic]
+    fn skip_vvec_short_length() {
+        let enc = Encoder::from_hex("ff");
+        let mut dec = enc.as_decoder();
+        dec.skip_vvec();
+    }
+
+    #[test]
+    fn encode_byte() {
+        let mut enc = Encoder::default();
+
+        enc.encode_byte(1);
+        assert_eq!(enc, Encoder::from_hex("01"));
+
+        enc.encode_byte(0xfe);
+        assert_eq!(enc, Encoder::from_hex("01fe"));
+    }
+
+    #[test]
+    fn encode() {
+        let mut enc = Encoder::default();
+        enc.encode(&[1, 2, 3]);
+        assert_eq!(enc, Encoder::from_hex("010203"));
+    }
+
+    #[test]
+    fn encode_uint() {
+        let mut enc = Encoder::default();
+        enc.encode_uint(2, 10_u8); // 000a
+        enc.encode_uint(1, 257_u16); // 01
+        enc.encode_uint(3, 0xff_ffff_u32); // ffffff
+        enc.encode_uint(8, 0xfedc_ba98_7654_3210_u64);
+        assert_eq!(enc, Encoder::from_hex("000a01fffffffedcba9876543210"));
+    }
+
+    #[test]
+    fn builder_from_slice() {
+        let slice = &[1, 2, 3];
+        let enc = Encoder::from(&slice[..]);
+        assert_eq!(enc, Encoder::from_hex("010203"))
+    }
+
+    #[test]
+    fn builder_inas_decoder() {
+        let enc = Encoder::from_hex("010203");
+        let buf = &[1, 2, 3];
+        assert_eq!(enc.as_decoder(), Decoder::new(buf));
+    }
+
+    struct UintTestCase {
+        v: u64,
+        b: String,
+    }
+
+    macro_rules! uint_tc {
+        [$( $v:expr => $b:expr ),+ $(,)?] => {
+            vec![ $( UintTestCase { v: $v, b: String::from($b) } ),+]
+        };
+    }
+
+    #[test]
+    fn varint_encode_decode() {
+        let cases = uint_tc![
+            0 => "00",
+            1 => "01",
+            63 => "3f",
+            64 => "4040",
+            16383 => "7fff",
+            16384 => "80004000",
+            (1 << 30) - 1 => "bfffffff",
+            1 << 30 => "c000000040000000",
+            (1 << 62) - 1 => "ffffffffffffffff",
+        ];
+
+        for c in cases {
+            assert_eq!(Encoder::varint_len(c.v), c.b.len() / 2);
+
+            let mut enc = Encoder::default();
+            enc.encode_varint(c.v);
+            let encoded = Encoder::from_hex(&c.b);
+            assert_eq!(enc, encoded);
+
+            let mut dec = encoded.as_decoder();
+            let v = dec.decode_varint().expect("should decode");
+            assert_eq!(dec.remaining(), 0);
+            assert_eq!(v, c.v);
+        }
+    }
+
+    #[test]
+    fn varint_decode_long_zero() {
+        for c in &["4000", "80000000", "c000000000000000"] {
+            let encoded = Encoder::from_hex(c);
+            let mut dec = encoded.as_decoder();
+            let v = dec.decode_varint().expect("should decode");
+            assert_eq!(dec.remaining(), 0);
+            assert_eq!(v, 0);
+        }
+    }
+
+    #[test]
+    fn varint_decode_short() {
+        for c in &["40", "800000", "c0000000000000"] {
+            let encoded = Encoder::from_hex(c);
+            let mut dec = encoded.as_decoder();
+            assert!(dec.decode_varint().is_none());
+        }
+    }
+
+    #[test]
+    fn encode_vec() {
+        let mut enc = Encoder::default();
+        enc.encode_vec(2, &[1, 2, 0x34]);
+        assert_eq!(enc, Encoder::from_hex("0003010234"));
+    }
+
+    #[test]
+    fn encode_vec_with() {
+        let mut enc = Encoder::default();
+        enc.encode_vec_with(2, |enc_inner| {
+            enc_inner.encode(&Encoder::from_hex("02"));
+        });
+        assert_eq!(enc, Encoder::from_hex("000102"));
+    }
+
+    #[test]
+    fn encode_vvec() {
+        let mut enc = Encoder::default();
+        enc.encode_vvec(&[1, 2, 0x34]);
+        assert_eq!(enc, Encoder::from_hex("03010234"));
+    }
+
+    #[test]
+    fn encode_vvec_with() {
+        let mut enc = Encoder::default();
+        enc.encode_vvec_with(|enc_inner| {
+            enc_inner.encode(&Encoder::from_hex("02"));
+        });
+        assert_eq!(enc, Encoder::from_hex("0102"));
+    }
+
+    #[test]
+    fn encode_vvec_with_longer() {
+        let mut enc = Encoder::default();
+        enc.encode_vvec_with(|enc_inner| {
+            enc_inner.encode(&[0xa5; 65]);
+        });
+        let mut v: Vec<u8> = enc.into();
+        v.split_off(3);
+        assert_eq!(v, vec![0x40, 0x41, 0xa5]);
+    }
+
+    // Test that Deref to &[u8] works for Encoder.
+    #[test]
+    fn encode_builder() {
+        let mut enc = Encoder::from_hex("ff");
+        let enc2 = Encoder::from_hex("010234");
+        enc.encode(&enc2);
+        assert_eq!(enc, Encoder::from_hex("ff010234"));
+    }
+
+    // Test that Deref to &[u8] works for Decoder.
+    #[test]
+    fn encode_view() {
+        let mut enc = Encoder::from_hex("ff");
+        let enc2 = Encoder::from_hex("010234");
+        let v = enc2.as_decoder();
+        enc.encode(&v);
+        assert_eq!(enc, Encoder::from_hex("ff010234"));
+    }
+
+    #[test]
+    fn encode_mutate() {
+        let mut enc = Encoder::from_hex("010234");
+        enc[0] = 0xff;
+        assert_eq!(enc, Encoder::from_hex("ff0234"));
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-common/src/datagram.rs
@@ -0,0 +1,40 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::net::SocketAddr;
+use std::ops::Deref;
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct Datagram {
+    src: SocketAddr,
+    dst: SocketAddr,
+    d: Vec<u8>,
+}
+
+impl Datagram {
+    pub fn new<V: Into<Vec<u8>>>(src: SocketAddr, dst: SocketAddr, d: V) -> Self {
+        Self {
+            src,
+            dst,
+            d: d.into(),
+        }
+    }
+
+    pub fn source(&self) -> SocketAddr {
+        self.src
+    }
+
+    pub fn destination(&self) -> SocketAddr {
+        self.dst
+    }
+}
+
+impl Deref for Datagram {
+    type Target = Vec<u8>;
+    fn deref(&self) -> &Self::Target {
+        &self.d
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-common/src/incrdecoder.rs
@@ -0,0 +1,420 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::convert::TryFrom;
+use std::mem;
+
+use crate::codec::Decoder;
+
+/// `IncrementalDecoder` incrementally processes input from incomplete input.
+/// Use this to process data that might be incomplete when it arrives.
+///
+/// ```
+/// use neqo_common::{Decoder, IncrementalDecoder, IncrementalDecoderResult};
+///
+/// let mut dec = IncrementalDecoder::decode_vec(1);
+/// let input = &[1, 0];
+/// match dec.consume(&mut Decoder::from(&input[..])) {
+///    IncrementalDecoderResult::Buffer(v) => println!("complete {:?}", v),
+///    IncrementalDecoderResult::InProgress => println!("still waiting"),
+///    _ => unreachable!(),
+/// }
+/// ```
+#[derive(Clone, Debug)]
+pub enum IncrementalDecoder {
+    Idle,
+    BeforeVarint,
+    InUint { v: u64, remaining: usize },
+    InBufferLen(Box<IncrementalDecoder>),
+    InBuffer { v: Vec<u8>, remaining: usize },
+    Ignoring { remaining: usize },
+}
+
+/// `IncrementalDecoderResult` is the result of decoding a partial input.
+/// You only get an error if the decoder completed its last instruction.
+#[derive(Debug, PartialEq)]
+pub enum IncrementalDecoderResult {
+    InProgress,
+    Uint(u64),
+    Buffer(Vec<u8>),
+    Ignored,
+    Error,
+}
+
+impl IncrementalDecoder {
+    /// Decode a buffer of fixed size.
+    pub fn decode(n: usize) -> Self {
+        IncrementalDecoder::InBuffer {
+            v: Vec::new(),
+            remaining: n,
+        }
+    }
+
+    /// Decode an unsigned integer of fixed size.
+    pub fn decode_uint(n: usize) -> Self {
+        IncrementalDecoder::InUint { v: 0, remaining: n }
+    }
+
+    /// Decode a QUIC variable-length integer.
+    pub fn decode_varint() -> Self {
+        IncrementalDecoder::BeforeVarint
+    }
+
+    /// Decode a vector that as a fixed-sized length prefix.
+    pub fn decode_vec(n: usize) -> Self {
+        IncrementalDecoder::InBufferLen(Box::new(Self::decode_uint(n)))
+    }
+
+    /// Decode a vector that as a varint-sized length prefix.
+    pub fn decode_vvec() -> Self {
+        IncrementalDecoder::InBufferLen(Box::new(Self::decode_varint()))
+    }
+
+    /// Ignore a certain number of bytes.
+    pub fn ignore(n: usize) -> Self {
+        IncrementalDecoder::Ignoring { remaining: n }
+    }
+
+    /// For callers that might need to request additional data, provide an indication
+    /// of the minimum amount of data that should be requested to make progress.
+    /// The guarantee is that this will never return a value larger than a subsequent
+    /// call to `consume()` will use.  This returns 0 if it is idle.
+    pub fn min_remaining(&self) -> usize {
+        match self {
+            IncrementalDecoder::BeforeVarint => 1,
+            IncrementalDecoder::InUint { remaining, .. }
+            | IncrementalDecoder::InBuffer { remaining, .. }
+            | IncrementalDecoder::Ignoring { remaining } => *remaining,
+            IncrementalDecoder::InBufferLen(in_len) => in_len.min_remaining(),
+            _ => 0,
+        }
+    }
+
+    fn consume_uint_part(mut v: u64, amount: usize, dv: &mut Decoder) -> u64 {
+        if amount == 0 {
+            v
+        } else {
+            if amount < 8 {
+                v <<= amount * 8;
+            }
+            v | dv.decode_uint(amount).unwrap()
+        }
+    }
+
+    fn consume_uint_remainder(
+        &mut self,
+        mut v: u64,
+        mut remaining: usize,
+        dv: &mut Decoder,
+    ) -> IncrementalDecoderResult {
+        if remaining <= dv.remaining() {
+            v = Self::consume_uint_part(v, remaining, dv);
+            *self = IncrementalDecoder::Idle;
+            IncrementalDecoderResult::Uint(v)
+        } else {
+            let r = dv.remaining();
+            v = Self::consume_uint_part(v, r, dv);
+            remaining -= r;
+            *self = IncrementalDecoder::InUint { v, remaining };
+            IncrementalDecoderResult::InProgress
+        }
+    }
+
+    fn consume_buffer_remainder(
+        &mut self,
+        mut v: Vec<u8>,
+        remaining: usize,
+        dv: &mut Decoder,
+    ) -> IncrementalDecoderResult {
+        if remaining <= dv.remaining() {
+            let b = dv.decode(remaining).unwrap();
+            v.extend_from_slice(b);
+            *self = IncrementalDecoder::Idle;
+            IncrementalDecoderResult::Buffer(v)
+        } else {
+            let b = dv.decode_remainder();
+            v.extend_from_slice(b);
+            *self = IncrementalDecoder::InBuffer {
+                v,
+                remaining: remaining - b.len(),
+            };
+            IncrementalDecoderResult::InProgress
+        }
+    }
+
+    /// Consume data incrementally from |dv|.
+    pub fn consume(&mut self, dv: &mut Decoder) -> IncrementalDecoderResult {
+        match mem::replace(self, IncrementalDecoder::Idle) {
+            IncrementalDecoder::Idle => IncrementalDecoderResult::Error,
+
+            IncrementalDecoder::InUint { v, remaining } => {
+                self.consume_uint_remainder(v, remaining, dv)
+            }
+
+            IncrementalDecoder::BeforeVarint => {
+                let (v, remaining) = match dv.decode_byte() {
+                    Some(b) => (
+                        u64::from(b & 0x3f),
+                        match b >> 6 {
+                            0 => 0,
+                            1 => 1,
+                            2 => 3,
+                            3 => 7,
+                            _ => unreachable!(),
+                        },
+                    ),
+                    None => return IncrementalDecoderResult::Error,
+                };
+                if remaining == 0 {
+                    *self = IncrementalDecoder::Idle;
+                    IncrementalDecoderResult::Uint(v)
+                } else {
+                    self.consume_uint_remainder(v, remaining, dv)
+                }
+            }
+
+            IncrementalDecoder::InBufferLen(mut len_decoder) => match len_decoder.consume(dv) {
+                IncrementalDecoderResult::InProgress => {
+                    *self = IncrementalDecoder::InBufferLen(len_decoder);
+                    IncrementalDecoderResult::InProgress
+                }
+                IncrementalDecoderResult::Uint(n) => {
+                    if let Ok(len) = usize::try_from(n) {
+                        self.consume_buffer_remainder(Vec::with_capacity(len), len, dv)
+                    } else {
+                        IncrementalDecoderResult::Error
+                    }
+                }
+                _ => unreachable!(),
+            },
+
+            IncrementalDecoder::InBuffer { v, remaining } => {
+                self.consume_buffer_remainder(v, remaining, dv)
+            }
+
+            IncrementalDecoder::Ignoring { remaining } => {
+                if remaining <= dv.remaining() {
+                    let _ = dv.decode(remaining);
+                    *self = IncrementalDecoder::Idle;
+                    IncrementalDecoderResult::Ignored
+                } else {
+                    *self = IncrementalDecoder::Ignoring {
+                        remaining: remaining - dv.remaining(),
+                    };
+                    let _ = dv.decode_remainder();
+                    IncrementalDecoderResult::InProgress
+                }
+            }
+        }
+    }
+}
+
+impl Default for IncrementalDecoder {
+    fn default() -> Self {
+        IncrementalDecoder::Idle
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::codec::Encoder;
+
+    #[test]
+    fn simple_incremental() {
+        let b = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+        let mut dec = IncrementalDecoder::decode(b.len());
+        let mut i = 0;
+        while i < b.len() {
+            // Feed in b in increasing-sized chunks.
+            let incr = if i < b.len() / 2 { i + 1 } else { b.len() - i };
+            let mut dv = Decoder::from(&b[i..i + incr]);
+            i += incr;
+            match dec.consume(&mut dv) {
+                IncrementalDecoderResult::InProgress => {
+                    assert!(i < b.len());
+                }
+                IncrementalDecoderResult::Buffer(res) => {
+                    assert_eq!(i, b.len());
+                    assert_eq!(res, b);
+                }
+                _ => unreachable!(),
+            }
+        }
+    }
+
+    // Take a |decoder|, an encoded buffer |db|, and an |expected| outcome and run
+    // the buffer through the encoder several times and check the result.
+    // Split the buffer in a different position each time.
+    fn run_split(
+        decoder: &IncrementalDecoder,
+        mut db: Encoder,
+        expected: &IncrementalDecoderResult,
+    ) {
+        eprintln!(
+            "decode {:?} ; with {:?} ; expect {:?}",
+            decoder, db, expected
+        );
+
+        // Add padding so that we can verify that the reader doesn't over-consume.
+        db.encode_byte(0xff);
+
+        for tail in 1..db.len() {
+            let split = db.len() - tail;
+            let mut dv = Decoder::from(&db[0..split]);
+            eprintln!("  split at {}: {:?}", split, dv);
+
+            // Clone the basic decoder for each iteration of the loop.
+            let mut dec = decoder.clone();
+            let mut res = dec.consume(&mut dv);
+            assert_eq!(dv.remaining(), 0);
+            assert!(dec.min_remaining() < tail);
+
+            if tail > 1 {
+                assert_eq!(res, IncrementalDecoderResult::InProgress);
+                assert!(dec.min_remaining() > 0);
+                let mut dv = Decoder::from(&db[split..]);
+                eprintln!("  split remainder {}: {:?}", split, dv);
+                res = dec.consume(&mut dv);
+                assert_eq!(dv.remaining(), 1);
+            }
+
+            assert_eq!(dec.min_remaining(), 0);
+            assert_eq!(&res, expected);
+        }
+    }
+
+    struct UintTestCase {
+        b: String,
+        v: u64,
+    }
+
+    impl UintTestCase {
+        pub fn run(&self, dec: &IncrementalDecoder) {
+            run_split(
+                &dec,
+                Encoder::from_hex(&self.b),
+                &IncrementalDecoderResult::Uint(self.v),
+            );
+        }
+    }
+
+    macro_rules! uint_tc {
+        [$( $b:expr => $v:expr ),+ $(,)?] => {
+            vec![ $( UintTestCase { b: String::from($b), v: $v, } ),+]
+        };
+    }
+
+    #[test]
+    fn uint() {
+        for c in uint_tc![
+            "00" => 0,
+            "0000000000" => 0,
+            "01" => 1,
+            "0123" => 0x123,
+            "012345" => 0x12345,
+            "ffffffffffffffff" => 0xffff_ffff_ffff_ffff,
+        ] {
+            c.run(&IncrementalDecoder::decode_uint(c.b.len() / 2));
+        }
+    }
+
+    #[test]
+    fn varint() {
+        for c in uint_tc![
+            "00" => 0,
+            "01" => 1,
+            "3f" => 63,
+            "4040" => 64,
+            "7fff" => 16383,
+            "80004000" => 16384,
+            "bfffffff" => (1 << 30) - 1,
+            "c000000040000000" => 1 << 30,
+            "ffffffffffffffff" => (1 << 62) - 1,
+        ] {
+            c.run(&IncrementalDecoder::decode_varint());
+        }
+    }
+
+    struct BufTestCase {
+        b: String,
+        v: String,
+    }
+
+    impl BufTestCase {
+        pub fn run(&self, dec: &IncrementalDecoder) {
+            run_split(
+                &dec,
+                Encoder::from_hex(&self.b),
+                &IncrementalDecoderResult::Buffer(Encoder::from_hex(&self.v).into()),
+            );
+        }
+    }
+
+    macro_rules! buf_tc {
+        [$( $b:expr => $v:expr ),+ $(,)?] => {
+            vec![ $( BufTestCase { b: String::from($b), v: String::from($v), } ),+]
+        };
+    }
+
+    #[test]
+    fn vec() {
+        for c in buf_tc![
+            "00" => "",
+            "0100" => "00",
+            "01ff" => "ff",
+            "03012345" => "012345",
+        ] {
+            c.run(&IncrementalDecoder::decode_vec(1));
+        }
+        for c in buf_tc![
+            "0000" => "",
+            "000100" => "00",
+            "0001ff" => "ff",
+            "0003012345" => "012345",
+        ] {
+            c.run(&IncrementalDecoder::decode_vec(2));
+        }
+    }
+
+    #[test]
+    fn vvec() {
+        for c in buf_tc![
+            "00" => "",
+            "0100" => "00",
+            "01ff" => "ff",
+            "03012345" => "012345",
+        ] {
+            c.run(&IncrementalDecoder::decode_vvec());
+        }
+    }
+
+    #[test]
+    fn zero_len() {
+        let enc = Encoder::from_hex("ff");
+        let mut dec = Decoder::new(&enc);
+        let mut incr = IncrementalDecoder::decode(0);
+        assert_eq!(
+            incr.consume(&mut dec),
+            IncrementalDecoderResult::Buffer(Vec::new())
+        );
+        match incr {
+            IncrementalDecoder::Idle => {}
+            _ => panic!("should be idle"),
+        };
+        assert_eq!(dec.remaining(), enc.len());
+    }
+
+    #[test]
+    fn ignore() {
+        let enc = Encoder::from_hex("12345678");
+        run_split(
+            &IncrementalDecoder::ignore(4),
+            enc,
+            &IncrementalDecoderResult::Ignored,
+        );
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-common/src/lib.rs
@@ -0,0 +1,41 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![deny(warnings)]
+
+mod codec;
+mod datagram;
+mod incrdecoder;
+pub mod log;
+pub mod once;
+pub mod timer;
+
+pub use self::codec::{Decoder, Encoder};
+pub use self::datagram::Datagram;
+pub use self::incrdecoder::{IncrementalDecoder, IncrementalDecoderResult};
+
+#[macro_use]
+extern crate lazy_static;
+
+// Cribbed from the |matches| crate, for simplicity.
+#[macro_export]
+macro_rules! matches {
+    ($expression:expr, $($pattern:tt)+) => {
+        match $expression {
+            $($pattern)+ => true,
+            _ => false
+        }
+    }
+}
+
+pub fn hex(buf: &[u8]) -> String {
+    let mut ret = String::with_capacity(10 + buf.len() * 3);
+    ret.push_str(&format!("[{}]: ", buf.len()));
+    for b in buf {
+        ret.push_str(&format!("{:02x}", b));
+    }
+    ret
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-common/src/log.rs
@@ -0,0 +1,76 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use env_logger::Builder;
+use std::io::Write;
+use std::sync::Once;
+use std::time::Instant;
+
+static INIT_ONCE: Once = Once::new();
+
+lazy_static! {
+    static ref START_TIME: Instant = Instant::now();
+}
+
+pub fn init() {
+    INIT_ONCE.call_once(|| {
+        let mut builder = Builder::from_env("RUST_LOG");
+        builder.format(|buf, record| {
+            let elapsed = START_TIME.elapsed();
+            writeln!(
+                buf,
+                "{}s{:3}ms {} {}",
+                elapsed.as_secs(),
+                elapsed.as_millis() % 1000,
+                buf.default_styled_level(record.level()),
+                record.args()
+            )
+        });
+        if let Err(e) = builder.try_init() {
+            ::log::log!(::log::Level::Info, "Logging initialization error {:?}", e);
+        } else {
+            ::log::log!(::log::Level::Info, "Logging initialized");
+        }
+    });
+}
+
+#[macro_export]
+macro_rules! qlog {
+    ($lvl:expr, $ctx:expr, $($arg:tt)*) => ( {
+        ::neqo_common::log::init();
+        ::log::log!($lvl, "[{}] {}", $ctx, format!($($arg)*));
+    } )
+}
+#[macro_export]
+macro_rules! qerror {
+    ([$ctx:expr], $($arg:tt)*) => (::neqo_common::qlog!(::log::Level::Error, $ctx, $($arg)*););
+    ([$ctx:expr] $($arg:tt)*) => (::neqo_common::qlog!(::log::Level::Error, $ctx, $($arg)*););
+    ($($arg:tt)*) => ( { ::neqo_common::log::init(); ::log::log!(::log::Level::Error, $($arg)*); } );
+}
+#[macro_export]
+macro_rules! qwarn {
+    ([$ctx:expr], $($arg:tt)*) => (::neqo_common::qlog!(::log::Level::Warn, $ctx, $($arg)*););
+    ([$ctx:expr] $($arg:tt)*) => (::neqo_common::qlog!(::log::Level::Warn, $ctx, $($arg)*););
+    ($($arg:tt)*) => ( { ::neqo_common::log::init(); ::log::log!(::log::Level::Warn, $($arg)*); } );
+}
+#[macro_export]
+macro_rules! qinfo {
+    ([$ctx:expr], $($arg:tt)*) => (::neqo_common::qlog!(::log::Level::Info, $ctx, $($arg)*););
+    ([$ctx:expr] $($arg:tt)*) => (::neqo_common::qlog!(::log::Level::Info, $ctx, $($arg)*););
+    ($($arg:tt)*) => ( { ::neqo_common::log::init(); ::log::log!(::log::Level::Info, $($arg)*); } );
+}
+#[macro_export]
+macro_rules! qdebug {
+    ([$ctx:expr], $($arg:tt)*) => (::neqo_common::qlog!(::log::Level::Debug, $ctx, $($arg)*););
+    ([$ctx:expr] $($arg:tt)*) => (::neqo_common::qlog!(::log::Level::Debug, $ctx, $($arg)*););
+    ($($arg:tt)*) => ( { ::neqo_common::log::init(); ::log::log!(::log::Level::Debug, $($arg)*); } );
+}
+#[macro_export]
+macro_rules! qtrace {
+    ([$ctx:expr], $($arg:tt)*) => (::neqo_common::qlog!(::log::Level::Trace, $ctx, $($arg)*););
+    ([$ctx:expr] $($arg:tt)*) => (::neqo_common::qlog!(::log::Level::Trace, $ctx, $($arg)*););
+    ($($arg:tt)*) => ( { ::neqo_common::log::init(); ::log::log!(::log::Level::Trace, $($arg)*); } );
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-common/src/once.rs
@@ -0,0 +1,43 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::sync::Once;
+
+#[allow(clippy::module_name_repetitions)]
+pub struct OnceResult<T> {
+    once: Once,
+    v: Option<T>,
+}
+
+impl<T> OnceResult<T> {
+    pub const fn new() -> Self {
+        Self {
+            once: Once::new(),
+            v: None,
+        }
+    }
+
+    pub fn call_once<F: FnOnce() -> T>(&mut self, f: F) -> &T {
+        let v = &mut self.v;
+        self.once.call_once(|| {
+            *v = Some(f());
+        });
+        self.v.as_ref().unwrap()
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    static mut STATIC_ONCE_RESULT: OnceResult<u64> = OnceResult::new();
+
+    #[test]
+    fn static_update() {
+        assert_eq!(*unsafe { STATIC_ONCE_RESULT.call_once(|| 23) }, 23);
+        assert_eq!(*unsafe { STATIC_ONCE_RESULT.call_once(|| 24) }, 23);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-common/src/timer.rs
@@ -0,0 +1,357 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::convert::TryFrom;
+use std::mem;
+use std::time::{Duration, Instant};
+
+/// Internal structure for a timer item.
+struct TimerItem<T> {
+    time: Instant,
+    item: T,
+}
+
+impl<T> TimerItem<T> {
+    fn time(ti: &Self) -> Instant {
+        ti.time
+    }
+}
+
+/// A timer queue.
+/// This uses a classic timer wheel arrangement, with some characteristics that might be considered peculiar.
+/// Each slot in the wheel is sorted (complexity O(N) insertions, but O(logN) to find cut points).
+/// Time is relative, the wheel has an origin time and it is unable to represent times that are more than
+/// `granularity * capacity` past that time.
+pub struct Timer<T> {
+    items: Vec<Vec<TimerItem<T>>>,
+    now: Instant,
+    granularity: Duration,
+    cursor: usize,
+}
+
+impl<T> Timer<T> {
+    /// Construct a new wheel at the given granularity, starting at the given time.
+    pub fn new(now: Instant, granularity: Duration, capacity: usize) -> Self {
+        assert!(u32::try_from(capacity).is_ok());
+        assert!(granularity.as_nanos() > 0);
+        let mut items = Vec::with_capacity(capacity);
+        items.resize_with(capacity, Default::default);
+        Self {
+            items,
+            now,
+            granularity,
+            cursor: 0,
+        }
+    }
+
+    /// Return a reference to the time of the next entry.
+    pub fn next_time(&self) -> Option<Instant> {
+        for i in 0..self.items.len() {
+            let idx = self.bucket(i);
+            if let Some(t) = self.items[idx].first() {
+                return Some(t.time);
+            }
+        }
+        None
+    }
+
+    /// Get the full span of time that this can cover.
+    /// Two timers cannot be more than this far apart.
+    /// In practice, this value is less by one amount of the timer granularity.
+    #[inline]
+    #[allow(clippy::cast_possible_truncation)] // guarded by assertion
+    pub fn span(&self) -> Duration {
+        self.granularity * (self.items.len() as u32)
+    }
+
+    /// For the given `time`, get the number of whole buckets in the future that is.
+    #[inline]
+    #[allow(clippy::cast_possible_truncation)] // guarded by assertion
+    fn delta(&self, time: Instant) -> usize {
+        // This really should use Instant::div_duration(), but it can't yet.
+        ((time - self.now).as_nanos() / self.granularity.as_nanos()) as usize
+    }
+
+    #[inline]
+    fn time_bucket(&self, time: Instant) -> usize {
+        self.bucket(self.delta(time))
+    }
+
+    #[inline]
+    fn bucket(&self, delta: usize) -> usize {
+        debug_assert!(delta < self.items.len());
+        (self.cursor + delta) % self.items.len()
+    }
+
+    /// Slide forward in time by `n * self.granularity`.
+    #[allow(clippy::cast_possible_truncation)] // guarded by assertion
+    fn tick(&mut self, n: usize) {
+        let new = self.bucket(n);
+        let iter = if new < self.cursor {
+            (self.cursor..self.items.len()).chain(0..new)
+        } else {
+            (self.cursor..new).chain(0..0)
+        };
+        for i in iter {
+            assert!(self.items[i].is_empty());
+        }
+        self.now += self.granularity * (n as u32);
+        self.cursor = new;
+    }
+
+    /// Asserts if the time given is in the past or too far in the future.
+    pub fn add(&mut self, time: Instant, item: T) {
+        assert!(time >= self.now);
+        // Skip forward quickly if there is too large a gap.
+        let short_span = self.span() - self.granularity;
+        if time >= (self.now + self.span() + short_span) {
+            // Assert that there aren't any items.
+            for i in &self.items {
+                assert!(i.is_empty());
+            }
+            self.now = time - short_span;
+            self.cursor = 0;
+        }
+
+        // Adjust time forward the minimum amount necessary.
+        let mut d = self.delta(time);
+        if d >= self.items.len() {
+            self.tick(1 + d - self.items.len());
+            d = self.items.len() - 1;
+        }
+
+        let bucket = self.bucket(d);
+        let ins = match self.items[bucket].binary_search_by_key(&time, TimerItem::time) {
+            Ok(j) | Err(j) => j,
+        };
+        self.items[bucket].insert(ins, TimerItem { time, item });
+    }
+
+    /// Given knowledge of the time an item was added, remove it.
+    /// This requires use of a predicate that identifies matching items.
+    pub fn remove<F>(&mut self, time: Instant, mut selector: F) -> Option<T>
+    where
+        F: FnMut(&T) -> bool,
+    {
+        if time < self.now {
+            return None;
+        }
+        let bucket = self.time_bucket(time);
+        let start_index = match self.items[bucket].binary_search_by_key(&time, TimerItem::time) {
+            Ok(idx) => idx,
+            _ => return None,
+        };
+        // start_index is just one of potentially many items with the same time.
+        // Search backwards for a match, ...
+        for i in (0..=start_index).rev() {
+            if self.items[bucket][i].time != time {
+                break;
+            }
+            if selector(&self.items[bucket][i].item) {
+                return Some(self.items[bucket].remove(i).item);
+            }
+        }
+        // ... then forwards.
+        for i in (start_index + 1)..self.items[bucket].len() {
+            if self.items[bucket][i].time != time {
+                break;
+            }
+            if selector(&self.items[bucket][i].item) {
+                return Some(self.items[bucket].remove(i).item);
+            }
+        }
+        None
+    }
+
+    /// Take the next item, unless there are no items with
+    /// a timeout in the past relative to `until`.
+    pub fn take_next(&mut self, until: Instant) -> Option<T> {
+        for i in 0..self.items.len() {
+            let idx = self.bucket(i);
+            if !self.items[idx].is_empty() && self.items[idx][0].time <= until {
+                return Some(self.items[idx].remove(0).item);
+            }
+        }
+        None
+    }
+
+    /// Create an iterator that takes all items until the given time.
+    /// Note: Items might be removed even if the iterator is not fully exhausted.
+    pub fn take_until(&mut self, until: Instant) -> impl Iterator<Item = T> {
+        let get_item = move |x: TimerItem<T>| x.item;
+        if until >= self.now + self.span() {
+            // Drain everything, so a clean sweep.
+            let mut empty_items = Vec::with_capacity(self.items.len());
+            empty_items.resize_with(self.items.len(), Vec::default);
+            let mut items = mem::replace(&mut self.items, empty_items);
+            self.now = until;
+            self.cursor = 0;
+
+            let tail = items.split_off(self.cursor);
+            return tail.into_iter().chain(items).flatten().map(get_item);
+        }
+
+        // Only returning a partial span, so do it bucket at a time.
+        let delta = self.delta(until);
+        let mut buckets = Vec::with_capacity(delta + 1);
+
+        // First, the whole buckets.
+        for i in 0..delta {
+            let idx = self.bucket(i);
+            buckets.push(mem::replace(&mut self.items[idx], Vec::default()));
+        }
+        self.tick(delta);
+
+        // Now we need to split the last bucket, because there might be
+        // some items with `item.time > until`.
+        let bucket = &mut self.items[self.cursor];
+        let last_idx = match bucket.binary_search_by_key(&until, TimerItem::time) {
+            Ok(mut m) => {
+                // If there are multiple values, the search will hit any of them.
+                // Make sure to get them all.
+                while m < bucket.len() && bucket[m].time == until {
+                    m += 1;
+                }
+                m
+            }
+            Err(ins) => ins,
+        };
+        let tail = bucket.split_off(last_idx);
+        buckets.push(mem::replace(bucket, tail));
+        // This tomfoolery with the empty vector ensures that
+        // the returned type here matches the one above precisely
+        // without having to invoke the `either` crate.
+        buckets.into_iter().chain(vec![]).flatten().map(get_item)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use lazy_static::lazy_static;
+
+    lazy_static! {
+        static ref NOW: Instant = Instant::now();
+    }
+
+    const GRANULARITY: Duration = Duration::from_millis(10);
+    const CAPACITY: usize = 10;
+    #[test]
+    fn create() {
+        let t: Timer<()> = Timer::new(*NOW, GRANULARITY, CAPACITY);
+        assert_eq!(t.span(), Duration::from_millis(100));
+        assert_eq!(None, t.next_time());
+    }
+
+    #[test]
+    fn immediate_entry() {
+        let mut t = Timer::new(*NOW, GRANULARITY, CAPACITY);
+        t.add(*NOW, 12);
+        assert_eq!(*NOW, t.next_time().expect("should have an entry"));
+        let values: Vec<_> = t.take_until(*NOW).collect();
+        assert_eq!(vec![12], values);
+    }
+
+    #[test]
+    fn same_time() {
+        let mut t = Timer::new(*NOW, GRANULARITY, CAPACITY);
+        let v1 = 12;
+        let v2 = 13;
+        t.add(*NOW, v1);
+        t.add(*NOW, v2);
+        assert_eq!(*NOW, t.next_time().expect("should have an entry"));
+        let values: Vec<_> = t.take_until(*NOW).collect();
+        assert!(values.contains(&v1));
+        assert!(values.contains(&v2));
+    }
+
+    #[test]
+    fn add() {
+        let mut t = Timer::new(*NOW, GRANULARITY, CAPACITY);
+        let near_future = *NOW + Duration::from_millis(17);
+        let v = 9;
+        t.add(near_future, v);
+        assert_eq!(near_future, t.next_time().expect("should return a value"));
+        let values: Vec<_> = t
+            .take_until(near_future - Duration::from_millis(1))
+            .collect();
+        assert!(values.is_empty());
+        let values: Vec<_> = t
+            .take_until(near_future + Duration::from_millis(1))
+            .collect();
+        assert!(values.contains(&v));
+    }
+
+    #[test]
+    fn add_future() {
+        let mut t = Timer::new(*NOW, GRANULARITY, CAPACITY);
+        let future = *NOW + Duration::from_millis(117);
+        let v = 9;
+        t.add(future, v);
+        assert_eq!(future, t.next_time().expect("should return a value"));
+        let values: Vec<_> = t.take_until(future).collect();
+        assert!(values.contains(&v));
+    }
+
+    #[test]
+    fn add_far_future() {
+        let mut t = Timer::new(*NOW, GRANULARITY, CAPACITY);
+        let far_future = *NOW + Duration::from_millis(892);
+        let v = 9;
+        t.add(far_future, v);
+        assert_eq!(far_future, t.next_time().expect("should return a value"));
+        let values: Vec<_> = t.take_until(far_future).collect();
+        assert!(values.contains(&v));
+    }
+
+    const TIMES: &[Duration] = &[
+        Duration::from_millis(40),
+        Duration::from_millis(91),
+        Duration::from_millis(6),
+        Duration::from_millis(3),
+        Duration::from_millis(22),
+        Duration::from_millis(40),
+    ];
+
+    fn with_times() -> Timer<usize> {
+        let mut t = Timer::new(*NOW, GRANULARITY, CAPACITY);
+        for (i, time) in TIMES.iter().enumerate() {
+            t.add(*NOW + *time, i);
+        }
+        assert_eq!(
+            *NOW + *TIMES.iter().min().unwrap(),
+            t.next_time().expect("should have a time")
+        );
+        t
+    }
+
+    #[test]
+    fn multiple_values() {
+        let mut t = with_times();
+        let values: Vec<_> = t.take_until(*NOW + *TIMES.iter().max().unwrap()).collect();
+        for i in 0..TIMES.len() {
+            assert!(values.contains(&i));
+        }
+    }
+
+    #[test]
+    fn take_far_future() {
+        let mut t = with_times();
+        let values: Vec<_> = t.take_until(*NOW + Duration::from_secs(100)).collect();
+        for i in 0..TIMES.len() {
+            assert!(values.contains(&i));
+        }
+    }
+
+    #[test]
+    fn remove_each() {
+        let mut t = with_times();
+        for (i, time) in TIMES.iter().enumerate() {
+            assert_eq!(Some(i), t.remove(*NOW + *time, |&x| x == i));
+        }
+        assert_eq!(None, t.next_time());
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-common/tests/log.rs
@@ -0,0 +1,60 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![deny(warnings)]
+
+use neqo_common::{qdebug, qerror, qinfo, qtrace, qwarn};
+
+#[test]
+fn basic() {
+    qerror!("error");
+    qwarn!("warn");
+    qinfo!("info");
+    qdebug!("debug");
+    qtrace!("trace");
+}
+
+#[test]
+fn args() {
+    let num = 1;
+    let obj = std::time::Instant::now();
+    qerror!("error {} {:?}", num, obj);
+    qwarn!("warn {} {:?}", num, obj);
+    qinfo!("info {} {:?}", num, obj);
+    qdebug!("debug {} {:?}", num, obj);
+    qtrace!("trace {} {:?}", num, obj);
+}
+
+#[test]
+fn context() {
+    let context = "context";
+    qerror!([context] "error");
+    qwarn!([context] "warn");
+    qinfo!([context] "info");
+    qdebug!([context] "debug");
+    qtrace!([context] "trace");
+}
+
+#[test]
+fn context_comma() {
+    let obj = vec![1, 2, 3];
+    let context = "context";
+    qerror!([context], "error {:?}", obj);
+    qwarn!([context], "warn {:?}", obj);
+    qinfo!([context], "info {:?}", obj);
+    qdebug!([context], "debug {:?}", obj);
+    qtrace!([context], "trace {:?}", obj);
+}
+
+#[test]
+fn context_expr() {
+    let context = vec![1, 2, 3];
+    qerror!([format!("{:x?}", context)], "error");
+    qwarn!([format!("{:x?}", context)], "warn");
+    qinfo!([format!("{:x?}", context)], "info");
+    qdebug!([format!("{:x?}", context)], "debug");
+    qtrace!([format!("{:x?}", context)], "trace");
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/.cargo-checksum.json
@@ -0,0 +1,1 @@
+{"files":{"Cargo.toml":"cbdd3475e335916c815176d6782d2b705d52c7f74dd9c7dc4b356d4b3fc910b5","TODO":"ac0f1c2ebcca03f5b3c0cc56c5aedbb030a4b511e438bc07a57361c789f91e9f","bindings/bindings.toml":"0f305bda9513e7fb4b521df79912ad5ba21784377b84f4b531895619e561f356","bindings/mozpkix.hpp":"77072c8bb0f6eb6bfe8cbadc111dcd92e0c79936d13f2e501aae1e5d289a6675","bindings/nspr_err.h":"2d5205d017b536c2d838bcf9bc4ec79f96dd50e7bb9b73892328781f1ee6629d","bindings/nspr_error.h":"e41c03c77b8c22046f8618832c9569fbcc7b26d8b9bbc35eea7168f35e346889","bindings/nspr_io.h":"085b289849ef0e77f88512a27b4d9bdc28252bd4d39c6a17303204e46ef45f72","bindings/nspr_time.h":"2e637fd338a5cf0fd3fb0070a47f474a34c2a7f4447f31b6875f5a9928d0a261","bindings/nss_ciphers.h":"95ec6344a607558b3c5ba8510f463b6295f3a2fb3f538a01410531045a5f62d1","bindings/nss_init.h":"ef49045063782fb612aff459172cc6a89340f15005808608ade5320ca9974310","bindings/nss_p11.h":"0b81e64fe6db49b2ecff94edd850be111ef99ec11220e88ceb1c67be90143a78","bindings/nss_secerr.h":"713e8368bdae5159af7893cfa517dabfe5103cede051dee9c9557c850a2defc6","bindings/nss_ssl.h":"af222fb957b989e392e762fa2125c82608a0053aff4fb97e556691646c88c335","bindings/nss_sslerr.h":"24b97f092183d8486f774cdaef5030d0249221c78343570d83a4ee5b594210ae","bindings/nss_sslopt.h":"b7807eb7abdad14db6ad7bc51048a46b065a0ea65a4508c95a12ce90e59d1eea","build.rs":"ebb685e47de005413d8e24370ba22576b45b0273aaf0a5116af96dcdc48d32c0","src/aead.rs":"bfbd4b72354fb7103ff31a2a600dd457e10fdc14ad670a5de1f5bb0f9b6e1504","src/agent.rs":"e00ed1bc3c0194c329ea46f2cae78fe4b4faa1c824c215b8b10e1de2fb985b60","src/agentio.rs":"615a805e0f27970755daa5bfe864b2e9b3b09309dfa4178b85f38a13ae6f7131","src/auth.rs":"846a4954186f0bcabc4084214ae216819215f3bccc33cc08d93abb9c5fefb7f2","src/cert.rs":"fd3fd2bbb38754bdcee3898549feae412943c9f719032531c1ad6e61783b5394","src/constants.rs":"75dec8e3c74326f492a115a0e7a487daba32eba30bcbd64d2223333b3caa4008","src/err.rs":"81d5c9b75457d4367607d0a456c276fa037c0f66a1c8df40af58ff42426c9890","src/exp.rs":"61586662407359c1ecb8ed4987bc3c702f26ba2e203a091a51b6d6363cbd510f","src/ext.rs":"f3cc5bfdd96d46affff43653258d91eb447251f83da19b37dd415129052413e3","src/hkdf.rs":"6d44f63493f0c558a23339f88fe766f8afdb0bda3dc11a79e8a99d3c8d0b6acb","src/hp.rs":"854ce7b9d44892fbb01ac4078b84266771a9254cebfea5b94e7f4b4a7fb1b946","src/lib.rs":"6c3540b4e54510f6a298f04093f44f6d957f30f475214fd3ec9e39fa4d98e386","src/p11.rs":"89df482ae683646add0f46333991f31fe996fdce67859a1ff37c090a4166ce6e","src/prio.rs":"0e213056f6bf0c797c2cfe13c6d14dbb64a64b1218fff21cbf36fb3309b852f9","src/replay.rs":"01eae2accfefbc26719fcccd4bcb8c1ea6400ab96fbb696ecdb8f32164f931a2","src/result.rs":"d76c7bc5e99c80a5a124909ab586cdb91d894856e52f5146430da43584a6d6c1","src/secrets.rs":"e929b69927d93b4bde3bd490c0ed9a4e1e4c5a047492259ab1dae7fbad885c22","src/selfencrypt.rs":"9bffad6af2f186f606bd7305a8528d76e66471a71f7103c7498b90507fb031e1","src/ssl.rs":"4c7c850777a1b4b7b66ad765e24a25780e64f24da08175b5cc722a840d35f693","src/time.rs":"4dffa6f4ac9cfc8db240c370fb04a7d7241c80793ecf6acda2d41d0bc94b0236","tests/aead.rs":"bedf985ba0b95a9c6db6a84f870d15062d821f3b24cb3cb9dfa89445be795d50","tests/agent.rs":"8b9ca3c182cf065b7668fd9c7e5885b1cde8bb1d0ea3afbb5fb7a3186d7a9d2e","tests/ext.rs":"b1d2f9d68d18e24df5f246e4ad6f80a0a0d98f824094a9fa447a580b02486d90","tests/handshake.rs":"2752bd6c575e7d28db2bce8484aa08ba08846f30aa0bb9aa07153d1763dab830","tests/hkdf.rs":"83300020a18d5485a1adcd3b806aed64fd008072589875112145f401340f3602","tests/hp.rs":"83f453a792ef17eb51a20b2764407c28a60620f5d3b917c8e237a121b32988df","tests/init.rs":"abb08d3d5d9610d62dc94535230ed4f52743d90b7059224b152caa8cf7cf43d7","tests/selfencrypt.rs":"365bc96be63d2e970bab7cf0134a59526513e1c1c3b854c34fa44fc8ed8c10d3"},"package":null}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "neqo-crypto"
+version = "0.1.4"
+authors = ["Martin Thomson <mt@lowentropy.net>"]
+edition = "2018"
+build = "build.rs"
+
+[dependencies]
+neqo-common = { path = "../neqo-common" }
+log = "0.4.0"
+
+[build-dependencies]
+bindgen = {version = "0.51", default-features = false}
+serde = "1.0"
+serde_derive = "1.0"
+toml = "0.4"
+
+[dev-dependencies]
+test-fixture = { path = "../test-fixture" }
+
+[features]
+gecko = []
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/TODO
@@ -0,0 +1,4 @@
+early data - API in place for inspection, but depends on resumption
+handle panics more gracefully for extension handlers
+client certificates
+read/write - probably never
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/bindings/bindings.toml
@@ -0,0 +1,230 @@
+# In this file, every section corresponds to a header file.
+# A corresponding binding file will be created in $OUT_DIR.
+
+[nss_ssl]
+types = [
+    "PRCList",
+    "PRUint16",
+    "PRUint64",
+    "PRUint8",
+    "SECStatus",
+    "SSLAeadContext",
+    "SSLExtensionHandler",
+    "SSLExtensionType",
+    "SSLExtensionWriter",
+    "SSLHelloRetryRequestAction",
+    "SSLHelloRetryRequestCallback",
+    "SSLNamedGroup",
+    "SSLProtocolVariant",
+    "SSLRecordWriteCallback",
+    "SSLResumptionTokenCallback",
+    "SSLSecretCallback",
+    "SSLSignatureScheme",
+    "SSLTimeFunc",
+]
+functions = [
+    "SSL_AlertSentCallback",
+    "SSL_AuthCertificateComplete",
+    "SSL_AuthCertificateHook",
+    "SSL_CipherPrefSet",
+    "SSL_ConfigServerCert",
+    "SSL_ConfigServerSessionIDCache",
+    "SSL_GetChannelInfo",
+    "SSL_GetExperimentalAPI",
+    "SSL_GetImplementedCiphers",
+    "SSL_GetNextProto",
+    "SSL_GetNumImplementedCiphers",
+    "SSL_GetPreliminaryChannelInfo",
+    "SSL_ForceHandshake",
+    "SSL_ImportFD",
+    "SSL_NamedGroupConfig",
+    "SSL_OptionSet",
+    "SSL_PeerCertificate",
+    "SSL_PeerCertificateChain",
+    "SSL_PeerSignedCertTimestamps",
+    "SSL_PeerStapledOCSPResponses",
+    "SSL_ResetHandshake",
+    "SSL_SetNextProtoNego",
+    "SSL_SetURL",
+    "SSL_VersionRangeSet",
+]
+enums = [
+    "SSLAuthType",
+    "SSLCipherAlgorithm",
+    "SSLCompressionMethod",
+    "SSLContentType",
+    "SSLExtensionType",
+    "SSLHandshakeType",
+    "SSLHelloRetryRequestAction",
+    "SSLKEAType",
+    "SSLMACAlgorithm",
+    "SSLNamedGroup",
+    "SSLNextProtoState",
+    "SSLProtocolVariant",
+    "SSLSecretDirection",
+    "SSLSignatureScheme",
+    "SECStatus",
+]
+variables = [
+    "SSL_LIBRARY_VERSION_TLS_\\d_\\d",
+    "SSL_NumImplementedCiphers",
+    "ssl_preinfo_.*",
+]
+opaque = [
+    "CERTCertificate",
+    "PK11SymKey",
+    "PLArenaPool",
+    "PRFileDesc",
+    "SECKEYPrivateKey",
+    "SECKEYPublicKey",
+    "SSLExtraServerCertData",
+]
+
+[nss_sslopt]
+variables = [
+    "SSL_REQUEST_CERTIFICATE",
+    "SSL_REQUIRE_CERTIFICATE",
+    "SSL_NO_LOCKS",
+    "SSL_ENABLE_SESSION_TICKETS",
+    "SSL_ENABLE_OCSP_STAPLING",
+    "SSL_ENABLE_ALPN",
+    "SSL_ENABLE_EXTENDED_MASTER_SECRET",
+    "SSL_ENABLE_SIGNED_CERT_TIMESTAMPS",
+    "SSL_ENABLE_0RTT_DATA",
+    "SSL_RECORD_SIZE_LIMIT",
+    "SSL_ENABLE_TLS13_COMPAT_MODE",
+    "SSL_ENABLE_HELLO_DOWNGRADE_CHECK",
+]
+
+[nss_ciphers]
+variables = ["TLS_.*"]
+exclude = [
+    ".*_(?:EXPORT(?:1024)?|anon|DES|RC4)_.*",
+    ".*_(?:MD5|NULL_SHA)",
+]
+
+[nss_secerr]
+types = ["SECErrorCodes"]
+enums = ["SECErrorCodes"]
+
+[nss_sslerr]
+types = ["SSLErrorCodes"]
+enums = ["SSLErrorCodes"]
+
+[nss_init]
+functions = [
+    "NSS_Initialize",
+    "NSS_IsInitialized",
+    "NSS_NoDB_Init",
+    "NSS_SetDomesticPolicy",
+    "NSS_Shutdown",
+]
+variables = [
+    "NSS_INIT_READONLY",
+    "SECMOD_DB",
+]
+
+[nss_p11]
+types = [
+    "CERTCertList",
+    "CERTCertListNode",
+    "SECItem",
+    "SECItemArray",
+    "CK_ATTRIBUTE_TYPE",
+    "CK_MECHANISM_TYPE",
+]
+functions = [
+    "CERT_DestroyCertificate",
+    "CERT_DestroyCertList",
+    "CERT_GetCertificateDer",
+    "PK11_Encrypt",
+    "PK11_ExtractKeyValue",
+    "PK11_FindCertFromNickname",
+    "PK11_FindKeyByAnyCert",
+    "PK11_FreeSlot",
+    "PK11_FreeSymKey",
+    "PK11_GenerateRandom",
+    "PK11_GetBlockSize",
+    "PK11_GetInternalSlot",
+    "PK11_GetKeyData",
+    "PK11_GetMechanism",
+    "PK11_ImportSymKey",
+    "PK11_ReferenceSymKey",
+    "SECKEY_DestroyPrivateKey",
+]
+enums = [
+    "PK11Origin",
+    "SECItemType",
+]
+opaque = [
+    "CERTCertificate",
+    "PK11SlotInfo",
+    "PK11SymKey",
+    "SECKEYPublicKey",
+    "SECKEYPrivateKey",
+]
+variables = [
+    "CKA_DERIVE",
+    "CKM_AES_ECB",
+    "CKM_AES_GCM",
+    "CKM_INVALID_MECHANISM",
+    "CKM_NSS_CHACHA20_POLY1305",
+    "CKM_NSS_CHACHA20_CTR",
+    "CKM_NSS_HKDF_SHA256",
+    "CKM_NSS_HKDF_SHA384",
+]
+
+[nspr_err]
+# NSPR doesn't use an enum for errors, so we have to pull in everything in the header file.
+# Specifying no types, functions, or variables does that, but then exclude some.
+exclude = [
+    "nspr_.*",
+    "PR_MAX_ERROR",
+    "ERROR_TABLE_BASE_nspr"
+]
+
+[nspr_error]
+functions = [
+    "PR_ErrorToName",
+    "PR_ErrorToString",
+    "PR_GetError",
+    "PR_SetError",
+]
+variables = [
+    "PR_LANGUAGE_I_DEFAULT",
+]
+
+[nspr_io]
+types = ["PRIOMethods"]
+functions = [
+    "PR_Close",
+    "PR_CreateIOLayerStub",
+    "PR_GetUniqueIdentity",
+]
+variables = [
+    "PR_AF_INET",
+]
+# opaque is for the stuff we don't plan to use, but we need for function signatures.
+opaque = [
+    "PRFileInfo",
+    "PRFileInfo64",
+    "PRFilePrivate",
+    "PRIOVec",
+    "PRSendFileData",
+]
+enums = [
+    "PRDescType",
+    "PRStatus",
+    "PRSeekWhence",
+    "PRSockOption",
+    "PRTransmitFileFlags",
+]
+
+[nspr_time]
+types = ["PRTime"]
+functions = ["PR_Now"]
+
+[mozpkix]
+cplusplus = true
+types = ["mozilla::pkix::ErrorCode"]
+enums = ["mozilla::pkix::ErrorCode"]
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/bindings/mozpkix.hpp
@@ -0,0 +1,1 @@
+#include "mozpkix/pkixnss.h"
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/bindings/nspr_err.h
@@ -0,0 +1,7 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#include "prerr.h"
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/bindings/nspr_error.h
@@ -0,0 +1,7 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#include "prerror.h"
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/bindings/nspr_io.h
@@ -0,0 +1,7 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#include "prio.h"
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/bindings/nspr_time.h
@@ -0,0 +1,7 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#include "prtime.h"
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/bindings/nss_ciphers.h
@@ -0,0 +1,8 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#define SSL_DISABLE_DEPRECATED_CIPHER_SUITE_NAMES
+#include "sslproto.h"
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/bindings/nss_init.h
@@ -0,0 +1,8 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#include "nss.h"
+#include "ssl.h" // For NSS_SetDomesticPolicy
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/bindings/nss_p11.h
@@ -0,0 +1,9 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#include "cert.h"
+#include "keyhi.h"
+#include "pk11pub.h"
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/bindings/nss_secerr.h
@@ -0,0 +1,7 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#include "secerr.h"
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/bindings/nss_ssl.h
@@ -0,0 +1,9 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#include "sslproto.h"
+#include "ssl.h"
+#include "sslexp.h"
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/bindings/nss_sslerr.h
@@ -0,0 +1,7 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#include "sslerr.h"
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/bindings/nss_sslopt.h
@@ -0,0 +1,7 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#include "ssl.h"
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/build.rs
@@ -0,0 +1,395 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![deny(warnings)]
+
+use bindgen::Builder;
+use serde_derive::Deserialize;
+use std::collections::HashMap;
+use std::env;
+use std::fs;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+use toml;
+
+const BINDINGS_DIR: &str = "bindings";
+const BINDINGS_CONFIG: &str = "bindings.toml";
+
+// This is the format of a single section of the configuration file.
+#[derive(Deserialize)]
+struct Bindings {
+    // types that are explicitly included
+    types: Option<Vec<String>>,
+    // functions that are explicitly included
+    functions: Option<Vec<String>>,
+    // variables (and `#define`s) that are explicitly included
+    variables: Option<Vec<String>>,
+    // types that should be explicitly marked as opaque
+    opaque: Option<Vec<String>>,
+    // enumerations that are turned into a module (without this, the enum is
+    // mapped using the default, which means that the individual values are
+    // formed with an underscore as <enum_type>_<enum_value_name>).
+    enums: Option<Vec<String>>,
+
+    // Any item that is specifically excluded; if none of the types, functions,
+    // or variables fields are specified, everything defined will be mapped,
+    // so this can be used to limit that.
+    exclude: Option<Vec<String>>,
+
+    // Whether the file is to be interpreted as C++
+    #[serde(default)]
+    cplusplus: bool,
+}
+
+fn is_debug() -> bool {
+    env::var("DEBUG")
+        .map(|d| d.parse::<bool>().unwrap_or(false))
+        .unwrap_or(false)
+}
+
+// bindgen needs access to libclang.
+// On windows, this doesn't just work, you have to set LIBCLANG_PATH.
+// Rather than download the 400Mb+ files, like gecko does, let's just reuse their work.
+fn setup_clang() {
+    if env::consts::OS != "windows" {
+        return;
+    }
+    println!("rerun-if-env-changed=LIBCLANG_PATH");
+    println!("rerun-if-env-changed=MOZBUILD_STATE_PATH");
+    if env::var("LIBCLANG_PATH").is_ok() {
+        return;
+    }
+    let mozbuild_root = match env::var("MOZBUILD_STATE_PATH") {
+        Ok(dir) => PathBuf::from(dir.trim()),
+        _ => {
+            eprintln!("warning: Building without a gecko setup is not likely to work.");
+            eprintln!("         A working libclang is needed to build neqo.");
+            eprintln!("         Either LIBCLANG_PATH or MOZBUILD_STATE_PATH needs to be set.");
+            eprintln!("");
+            eprintln!("    We recommend checking out https://github.com/mozilla/gecko-dev");
+            eprintln!("    Then run `./mach bootstrap` which will retrieve clang.");
+            eprintln!("    Make sure to export MOZBUILD_STATE_PATH when building.");
+            return;
+        }
+    };
+    let libclang_dir = mozbuild_root.join("clang").join("lib");
+    if libclang_dir.is_dir() {
+        env::set_var("LIBCLANG_PATH", libclang_dir.to_str().unwrap());
+        println!("rustc-env:LIBCLANG_PATH={}", libclang_dir.to_str().unwrap());
+    } else {
+        println!("warning: LIBCLANG_PATH isn't set; maybe run ./mach bootstrap with gecko");
+    }
+}
+
+fn nss_dir() -> PathBuf {
+    let dir = match env::var("NSS_DIR") {
+        Ok(dir) => PathBuf::from(dir.trim()),
+        _ => {
+            let out_dir = env::var("OUT_DIR").unwrap();
+            let dir = Path::new(&out_dir).join("nss");
+            if !dir.exists() {
+                Command::new("hg")
+                    .args(&[
+                        "clone",
+                        "https://hg.mozilla.org/projects/nss",
+                        dir.to_str().unwrap(),
+                    ])
+                    .status()
+                    .expect("can't clone nss");
+            }
+            let nspr_dir = Path::new(&out_dir).join("nspr");
+            if !nspr_dir.exists() {
+                Command::new("hg")
+                    .args(&[
+                        "clone",
+                        "https://hg.mozilla.org/projects/nspr",
+                        nspr_dir.to_str().unwrap(),
+                    ])
+                    .status()
+                    .expect("can't clone nspr");
+            }
+            dir.to_path_buf()
+        }
+    };
+    assert!(dir.is_dir());
+    // Note that this returns a relative path because UNC
+    // paths on windows cause certain tools to explode.
+    dir
+}
+
+fn get_bash() -> PathBuf {
+    // When running under MOZILLABUILD, we need to make sure not to invoke
+    // another instance of bash that might be sitting around (like WSL).
+    match env::var("MOZILLABUILD") {
+        Ok(d) => PathBuf::from(d).join("msys").join("bin").join("bash.exe"),
+        _ => PathBuf::from("bash"),
+    }
+}
+
+fn build_nss(dir: PathBuf) {
+    let mut build_nss = vec![String::from("./build.sh")];
+    if is_debug() {
+        build_nss.push(String::from("--static"));
+    } else {
+        build_nss.push(String::from("-o"));
+    }
+    match env::var("NSS_JOBS") {
+        Ok(d) => {
+            build_nss.push(String::from("-j"));
+            build_nss.push(d);
+        }
+        _ => (),
+    }
+    let status = Command::new(get_bash())
+        .args(build_nss)
+        .current_dir(dir)
+        .status()
+        .expect("couldn't start NSS build");
+    assert!(status.success(), "NSS build failed");
+}
+
+fn dynamic_link() {
+    let libs = if env::consts::OS == "windows" {
+        &["nssutil3.dll", "nss3.dll", "ssl3.dll"]
+    } else {
+        &["nssutil3", "nss3", "ssl3"]
+    };
+    dynamic_link_both(libs);
+}
+
+fn dynamic_link_both(extra_libs: &[&str]) {
+    let nspr_libs = if env::consts::OS == "windows" {
+        &["libplds4", "libplc4", "libnspr4"]
+    } else {
+        &["plds4", "plc4", "nspr4"]
+    };
+    for lib in nspr_libs.iter().chain(extra_libs) {
+        println!("cargo:rustc-link-lib=dylib={}", lib);
+    }
+}
+
+fn static_link(nsstarget: &PathBuf) {
+    let lib_dir = nsstarget.join("lib");
+    println!(
+        "cargo:rustc-link-search=native={}",
+        lib_dir.to_str().unwrap()
+    );
+    let mut static_libs = vec![
+        "certdb",
+        "certhi",
+        "cryptohi",
+        "dbm",
+        "freebl",
+        "nss_static",
+        "nssb",
+        "nssdev",
+        "nsspki",
+        "nssutil",
+        "pk11wrap",
+        "pkcs12",
+        "pkcs7",
+        "smime",
+        "softokn_static",
+        "ssl",
+    ];
+    if env::consts::OS != "macos" {
+        static_libs.push("sqlite");
+    }
+    for lib in static_libs {
+        println!("cargo:rustc-link-lib=static={}", lib);
+    }
+
+    // Dynamic libs that aren't transitively included by NSS libs.
+    let mut other_libs = Vec::new();
+    if env::consts::OS != "windows" {
+        other_libs.extend_from_slice(&["pthread", "dl", "c", "z"]);
+    }
+    if env::consts::OS == "macos" {
+        other_libs.push("sqlite3");
+    }
+    dynamic_link_both(&other_libs);
+}
+
+fn get_includes(nsstarget: &Path, nssdist: &Path) -> Vec<PathBuf> {
+    let nsprinclude = nsstarget.join("include").join("nspr");
+    let nssinclude = nssdist.join("public").join("nss");
+    let includes = vec![nsprinclude, nssinclude];
+    for i in &includes {
+        println!("cargo:include={}", i.to_str().unwrap());
+    }
+    includes
+}
+
+fn build_bindings(base: &str, bindings: &Bindings, flags: &[String], gecko: bool) {
+    let suffix = if bindings.cplusplus { ".hpp" } else { ".h" };
+    let header_path = PathBuf::from(BINDINGS_DIR).join(String::from(base) + suffix);
+    let header = header_path.to_str().unwrap();
+    let out = PathBuf::from(env::var("OUT_DIR").unwrap()).join(String::from(base) + ".rs");
+
+    println!("cargo:rerun-if-changed={}", header);
+
+    let mut builder = Builder::default().header(header).generate_comments(false);
+
+    builder = builder.clang_arg("-v");
+
+    if !gecko {
+        builder = builder.clang_arg("-DNO_NSPR_10_SUPPORT");
+        if env::consts::OS == "windows" {
+            builder = builder.clang_arg("-DWIN");
+        } else if env::consts::OS == "macos" {
+            builder = builder.clang_arg("-DDARWIN");
+        } else if env::consts::OS == "linux" {
+            builder = builder.clang_arg("-DLINUX");
+        } else if env::consts::OS == "android" {
+            builder = builder.clang_arg("-DLINUX");
+            builder = builder.clang_arg("-DANDROID");
+        }
+        if bindings.cplusplus {
+            builder = builder.clang_args(&["-x", "c++", "-std=c++11"]);
+        }
+    }
+
+    builder = builder.clang_args(flags);
+
+    // Apply the configuration.
+    let empty: Vec<String> = vec![];
+    for v in bindings.types.as_ref().unwrap_or_else(|| &empty).iter() {
+        builder = builder.whitelist_type(v);
+    }
+    for v in bindings.functions.as_ref().unwrap_or_else(|| &empty).iter() {
+        builder = builder.whitelist_function(v);
+    }
+    for v in bindings.variables.as_ref().unwrap_or_else(|| &empty).iter() {
+        builder = builder.whitelist_var(v);
+    }
+    for v in bindings.exclude.as_ref().unwrap_or_else(|| &empty).iter() {
+        builder = builder.blacklist_item(v);
+    }
+    for v in bindings.opaque.as_ref().unwrap_or_else(|| &empty).iter() {
+        builder = builder.opaque_type(v);
+    }
+    for v in bindings.enums.as_ref().unwrap_or_else(|| &empty).iter() {
+        builder = builder.constified_enum_module(v);
+    }
+
+    let bindings = builder.generate().expect("unable to generate bindings");
+    bindings
+        .write_to_file(out)
+        .expect("couldn't write bindings");
+}
+
+fn setup_standalone() -> Vec<String> {
+    setup_clang();
+
+    println!("cargo:rerun-if-env-changed=NSS_DIR");
+    let nss = nss_dir();
+    build_nss(nss.clone());
+
+    // $NSS_DIR/../dist/
+    let nssdist = nss.parent().unwrap().join("dist");
+    println!("cargo:rerun-if-env-changed=NSS_TARGET");
+    let nsstarget = env::var("NSS_TARGET")
+        .unwrap_or_else(|_| fs::read_to_string(nssdist.join("latest")).unwrap());
+    let nsstarget = nssdist.join(nsstarget.trim());
+
+    let includes = get_includes(&nsstarget, &nssdist);
+
+    let nsslibdir = nsstarget.join("lib");
+    println!(
+        "cargo:rustc-link-search=native={}",
+        nsslibdir.to_str().unwrap()
+    );
+
+    if is_debug() {
+        static_link(&nsstarget);
+    } else {
+        dynamic_link();
+    }
+
+    let mut flags: Vec<String> = Vec::new();
+    for i in includes {
+        flags.push(String::from("-I") + i.to_str().unwrap());
+    }
+
+    flags
+}
+
+fn setup_for_gecko() -> Vec<String> {
+    let mut flags: Vec<String> = Vec::new();
+
+    let libs = match env::var("CARGO_CFG_TARGET_OS").as_ref().map(|x| x.as_str()) {
+        Ok("android") | Ok("macos") => vec!["nss3"],
+        _ => vec!["nssutil3", "nss3", "ssl3", "plds4", "plc4", "nspr4"],
+    };
+
+    for lib in libs.iter() {
+        println!("cargo:rustc-link-lib=dylib={}", lib);
+    }
+
+    if let Some(path) = env::var_os("MOZ_TOPOBJDIR").map(PathBuf::from) {
+        println!(
+            "cargo:rustc-link-search=native={}",
+            path.join("dist").join("bin").to_str().unwrap()
+        );
+        let nsslib_path = path.clone().join("security").join("nss").join("lib");
+        println!(
+            "cargo:rustc-link-search=native={}",
+            nsslib_path.join("nss").join("nss_nss3").to_str().unwrap()
+        );
+        println!(
+            "cargo:rustc-link-search=native={}",
+            nsslib_path.join("ssl").join("ssl_ssl3").to_str().unwrap()
+        );
+        println!(
+            "cargo:rustc-link-search=native={}",
+            path.join("config")
+                .join("external")
+                .join("nspr")
+                .join("pr")
+                .to_str()
+                .unwrap()
+        );
+
+        let flags_path = path.join("netwerk/socket/neqo/extra-bindgen-flags");
+
+        println!("cargo:rerun-if-changed={}", flags_path.to_str().unwrap());
+        flags = fs::read_to_string(flags_path)
+            .expect("Failed to read extra-bindgen-flags file")
+            .split_whitespace()
+            .map(|s| s.to_owned())
+            .collect();
+
+        flags.push(String::from("-include"));
+        flags.push(
+            path.join("dist")
+                .join("include")
+                .join("mozilla-config.h")
+                .to_str()
+                .unwrap()
+                .to_string(),
+        );
+    } else {
+        println!("cargo:warning={}", "MOZ_TOPOBJDIR should be set by default, otherwise the build is not guaranteed to finish.");
+    }
+    flags
+}
+
+fn main() {
+    let flags = if cfg!(feature = "gecko") {
+        setup_for_gecko()
+    } else {
+        setup_standalone()
+    };
+
+    let config_file = PathBuf::from(BINDINGS_DIR).join(BINDINGS_CONFIG);
+    println!("cargo:rerun-if-changed={}", config_file.to_str().unwrap());
+    let config = fs::read_to_string(config_file).expect("unable to read binding configuration");
+    let config: HashMap<String, Bindings> = toml::from_str(&config).unwrap();
+
+    for (k, v) in &config {
+        build_bindings(k, v, &flags[..], cfg!(feature = "gecko"));
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/aead.rs
@@ -0,0 +1,152 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::constants::*;
+use crate::err::{Error, Res};
+use crate::p11::{PK11SymKey, SymKey};
+use crate::ssl;
+use crate::ssl::{PRUint16, PRUint64, PRUint8, SSLAeadContext};
+
+use std::convert::{TryFrom, TryInto};
+use std::fmt;
+use std::ops::{Deref, DerefMut};
+use std::os::raw::{c_char, c_uint};
+use std::ptr::{null_mut, NonNull};
+
+experimental_api!(SSL_MakeAead(
+    version: PRUint16,
+    cipher: PRUint16,
+    secret: *mut PK11SymKey,
+    label_prefix: *const c_char,
+    label_prefix_len: c_uint,
+    ctx: *mut *mut SSLAeadContext,
+));
+experimental_api!(SSL_AeadEncrypt(
+    ctx: *const SSLAeadContext,
+    counter: PRUint64,
+    aad: *const PRUint8,
+    aad_len: c_uint,
+    input: *const PRUint8,
+    input_len: c_uint,
+    output: *const PRUint8,
+    output_len: *mut c_uint,
+    max_output: c_uint
+));
+experimental_api!(SSL_AeadDecrypt(
+    ctx: *const SSLAeadContext,
+    counter: PRUint64,
+    aad: *const PRUint8,
+    aad_len: c_uint,
+    input: *const PRUint8,
+    input_len: c_uint,
+    output: *const PRUint8,
+    output_len: *mut c_uint,
+    max_output: c_uint
+));
+experimental_api!(SSL_DestroyAead(ctx: *mut SSLAeadContext));
+scoped_ptr!(AeadContext, SSLAeadContext, SSL_DestroyAead);
+
+pub struct Aead {
+    ctx: AeadContext,
+}
+
+impl Aead {
+    pub fn new(version: Version, cipher: Cipher, secret: &SymKey, prefix: &str) -> Res<Self> {
+        let s: *mut PK11SymKey = **secret;
+        unsafe { Self::from_raw(version, cipher, s, prefix) }
+    }
+
+    pub fn expansion(&self) -> usize {
+        16
+    }
+
+    unsafe fn from_raw(
+        version: Version,
+        cipher: Cipher,
+        secret: *mut PK11SymKey,
+        prefix: &str,
+    ) -> Res<Self> {
+        let p = prefix.as_bytes();
+        let mut ctx: *mut ssl::SSLAeadContext = null_mut();
+        SSL_MakeAead(
+            version,
+            cipher,
+            secret,
+            p.as_ptr() as *const c_char,
+            c_uint::try_from(p.len())?,
+            &mut ctx,
+        )?;
+        match NonNull::new(ctx) {
+            Some(ctx_ptr) => Ok(Self {
+                ctx: AeadContext::new(ctx_ptr),
+            }),
+            None => Err(Error::InternalError),
+        }
+    }
+
+    /// Decrypt a plaintext.
+    ///
+    /// The space provided in `output` needs to be larger than `input` by
+    /// the value provided in `Aead::expansion`.
+    pub fn encrypt<'a>(
+        &self,
+        count: u64,
+        aad: &[u8],
+        input: &[u8],
+        output: &'a mut [u8],
+    ) -> Res<&'a [u8]> {
+        let mut l: c_uint = 0;
+        unsafe {
+            SSL_AeadEncrypt(
+                *self.ctx.deref(),
+                count,
+                aad.as_ptr(),
+                c_uint::try_from(aad.len())?,
+                input.as_ptr(),
+                c_uint::try_from(input.len())?,
+                output.as_mut_ptr(),
+                &mut l,
+                c_uint::try_from(output.len())?,
+            )
+        }?;
+        Ok(&output[0..(l.try_into()?)])
+    }
+
+    /// Decrypt a ciphertext.
+    ///
+    /// Note that NSS insists upon having extra space available for decryption, so
+    /// the buffer for `output` should be the same length as `input`, even though
+    /// the final result will be shorter.
+    pub fn decrypt<'a>(
+        &self,
+        count: u64,
+        aad: &[u8],
+        input: &[u8],
+        output: &'a mut [u8],
+    ) -> Res<&'a [u8]> {
+        let mut l: c_uint = 0;
+        unsafe {
+            SSL_AeadDecrypt(
+                *self.ctx.deref(),
+                count,
+                aad.as_ptr(),
+                c_uint::try_from(aad.len())?,
+                input.as_ptr(),
+                c_uint::try_from(input.len())?,
+                output.as_mut_ptr(),
+                &mut l,
+                c_uint::try_from(output.len())?,
+            )
+        }?;
+        Ok(&output[0..(l.try_into()?)])
+    }
+}
+
+impl fmt::Debug for Aead {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "[AEAD Context]")
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/agent.rs
@@ -0,0 +1,915 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::agentio::{AgentIo, METHODS};
+pub use crate::agentio::{Record, RecordList};
+use crate::assert_initialized;
+use crate::auth::AuthenticationStatus;
+pub use crate::cert::CertificateInfo;
+use crate::constants::*;
+use crate::err::{is_blocked, secstatus_to_res, Error, PRErrorCode, Res};
+use crate::ext::{ExtensionHandler, ExtensionTracker};
+use crate::p11;
+use crate::prio;
+use crate::replay::AntiReplay;
+use crate::secrets::SecretHolder;
+use crate::ssl::{self, PRBool};
+use crate::time::{PRTime, Time};
+
+use neqo_common::{qdebug, qinfo, qwarn};
+use std::cell::RefCell;
+use std::convert::{TryFrom, TryInto};
+use std::ffi::CString;
+use std::mem;
+use std::ops::{Deref, DerefMut};
+use std::os::raw::{c_uint, c_void};
+use std::ptr::{null, null_mut, NonNull};
+use std::rc::Rc;
+use std::time::Instant;
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum HandshakeState {
+    New,
+    InProgress,
+    AuthenticationPending,
+    Authenticated(PRErrorCode),
+    Complete(SecretAgentInfo),
+    Failed(Error),
+}
+
+impl HandshakeState {
+    pub fn connected(&self) -> bool {
+        match self {
+            HandshakeState::Complete(_) => true,
+            _ => false,
+        }
+    }
+}
+
+fn get_alpn(fd: *mut ssl::PRFileDesc, pre: bool) -> Res<Option<String>> {
+    let mut alpn_state = ssl::SSLNextProtoState::SSL_NEXT_PROTO_NO_SUPPORT;
+    let mut chosen = vec![0_u8; 255];
+    let mut chosen_len: c_uint = 0;
+    secstatus_to_res(unsafe {
+        ssl::SSL_GetNextProto(
+            fd,
+            &mut alpn_state,
+            chosen.as_mut_ptr(),
+            &mut chosen_len,
+            c_uint::try_from(chosen.len())?,
+        )
+    })?;
+
+    let alpn = match (pre, alpn_state) {
+        (true, ssl::SSLNextProtoState::SSL_NEXT_PROTO_EARLY_VALUE)
+        | (false, ssl::SSLNextProtoState::SSL_NEXT_PROTO_NEGOTIATED)
+        | (false, ssl::SSLNextProtoState::SSL_NEXT_PROTO_SELECTED) => {
+            chosen.truncate(chosen_len as usize);
+            Some(match String::from_utf8(chosen) {
+                Ok(a) => a,
+                _ => return Err(Error::InternalError),
+            })
+        }
+        _ => None,
+    };
+    qinfo!([format!("{:p}", fd)] "got ALPN {:?}", alpn);
+    Ok(alpn)
+}
+
+pub struct SecretAgentPreInfo {
+    info: ssl::SSLPreliminaryChannelInfo,
+    alpn: Option<String>,
+}
+
+macro_rules! preinfo_arg {
+    ($v:ident, $m:ident, $f:ident: $t:ident $(,)?) => {
+        pub fn $v(&self) -> Option<$t> {
+            match self.info.valuesSet & ssl::$m {
+                0 => None,
+                _ => Some(self.info.$f as $t)
+            }
+        }
+    };
+}
+
+impl SecretAgentPreInfo {
+    fn new(fd: *mut ssl::PRFileDesc) -> Res<Self> {
+        let mut info: ssl::SSLPreliminaryChannelInfo = unsafe { mem::uninitialized() };
+        secstatus_to_res(unsafe {
+            ssl::SSL_GetPreliminaryChannelInfo(
+                fd,
+                &mut info,
+                c_uint::try_from(mem::size_of::<ssl::SSLPreliminaryChannelInfo>())?,
+            )
+        })?;
+
+        Ok(Self {
+            info,
+            alpn: get_alpn(fd, true)?,
+        })
+    }
+
+    preinfo_arg!(version, ssl_preinfo_version, protocolVersion: Version);
+    preinfo_arg!(cipher_suite, ssl_preinfo_cipher_suite, cipherSuite: Cipher);
+
+    pub fn early_data(&self) -> bool {
+        self.info.canSendEarlyData != 0
+    }
+
+    pub fn max_early_data(&self) -> usize {
+        self.info.maxEarlyDataSize as usize
+    }
+
+    pub fn alpn(&self) -> Option<&String> {
+        self.alpn.as_ref()
+    }
+
+    preinfo_arg!(
+        early_data_cipher,
+        ssl_preinfo_0rtt_cipher_suite,
+        zeroRttCipherSuite: Cipher,
+    );
+}
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct SecretAgentInfo {
+    version: Version,
+    cipher: Cipher,
+    group: Group,
+    resumed: bool,
+    early_data: bool,
+    alpn: Option<String>,
+    signature_scheme: SignatureScheme,
+}
+
+impl SecretAgentInfo {
+    fn new(fd: *mut ssl::PRFileDesc) -> Res<Self> {
+        let mut info: ssl::SSLChannelInfo = unsafe { mem::uninitialized() };
+        secstatus_to_res(unsafe {
+            ssl::SSL_GetChannelInfo(
+                fd,
+                &mut info,
+                c_uint::try_from(mem::size_of::<ssl::SSLChannelInfo>())?,
+            )
+        })?;
+        Ok(Self {
+            version: info.protocolVersion as Version,
+            cipher: info.cipherSuite as Cipher,
+            group: Group::try_from(info.keaGroup)?,
+            resumed: info.resumed != 0,
+            early_data: info.earlyDataAccepted != 0,
+            alpn: get_alpn(fd, false)?,
+            signature_scheme: SignatureScheme::try_from(info.signatureScheme)?,
+        })
+    }
+
+    pub fn version(&self) -> Version {
+        self.version
+    }
+    pub fn cipher_suite(&self) -> Cipher {
+        self.cipher
+    }
+    pub fn key_exchange(&self) -> Group {
+        self.group
+    }
+    pub fn resumed(&self) -> bool {
+        self.resumed
+    }
+    pub fn early_data_accepted(&self) -> bool {
+        self.early_data
+    }
+    pub fn alpn(&self) -> Option<&String> {
+        self.alpn.as_ref()
+    }
+    pub fn signature_scheme(&self) -> SignatureScheme {
+        self.signature_scheme
+    }
+}
+
+/// `SecretAgent` holds the common parts of client and server.
+#[derive(Debug)]
+#[allow(clippy::module_name_repetitions)]
+pub struct SecretAgent {
+    fd: *mut ssl::PRFileDesc,
+    secrets: SecretHolder,
+    raw: Option<bool>,
+    io: Box<AgentIo>,
+    state: HandshakeState,
+
+    /// Records whether authentication of certificates is required.
+    auth_required: Box<bool>,
+    /// Records any fatal alert that is sent by the stack.
+    alert: Box<Option<Alert>>,
+    /// The current time.
+    now: Box<PRTime>,
+
+    extension_handlers: Vec<ExtensionTracker>,
+    inf: Option<SecretAgentInfo>,
+
+    /// Whether or not EndOfEarlyData should be suppressed.
+    no_eoed: bool,
+}
+
+impl SecretAgent {
+    fn new() -> Res<Self> {
+        let mut agent = Self {
+            fd: null_mut(),
+            secrets: SecretHolder::default(),
+            raw: None,
+            io: Box::new(AgentIo::new()),
+            state: HandshakeState::New,
+
+            auth_required: Box::new(false),
+            alert: Box::new(None),
+            now: Box::new(0),
+
+            extension_handlers: Vec::new(),
+            inf: None,
+
+            no_eoed: false,
+        };
+        agent.create_fd()?;
+        Ok(agent)
+    }
+
+    // Create a new SSL file descriptor.
+    //
+    // Note that we create separate bindings for PRFileDesc as both
+    // ssl::PRFileDesc and prio::PRFileDesc.  This keeps the bindings
+    // minimal, but it means that the two forms need casts to translate
+    // between them.  ssl::PRFileDesc is left as an opaque type, as the
+    // ssl::SSL_* APIs only need an opaque type.
+    fn create_fd(&mut self) -> Res<()> {
+        assert_initialized();
+        let label = CString::new("sslwrapper")?;
+        let id = unsafe { prio::PR_GetUniqueIdentity(label.as_ptr()) };
+
+        let base_fd = unsafe { prio::PR_CreateIOLayerStub(id, METHODS) };
+        if base_fd.is_null() {
+            return Err(Error::CreateSslSocket);
+        }
+        let fd = unsafe {
+            (*base_fd).secret = &mut *self.io as *mut AgentIo as *mut _;
+            ssl::SSL_ImportFD(null_mut(), base_fd as *mut ssl::PRFileDesc)
+        };
+        if fd.is_null() {
+            unsafe { prio::PR_Close(base_fd) };
+            return Err(Error::CreateSslSocket);
+        }
+        self.fd = fd;
+        Ok(())
+    }
+
+    unsafe extern "C" fn auth_complete_hook(
+        arg: *mut c_void,
+        _fd: *mut ssl::PRFileDesc,
+        _check_sig: ssl::PRBool,
+        _is_server: ssl::PRBool,
+    ) -> ssl::SECStatus {
+        let auth_required_ptr = arg as *mut bool;
+        *auth_required_ptr = true;
+        // NSS insists on getting SECWouldBlock here rather than accepting
+        // the usual combination of PR_WOULD_BLOCK_ERROR and SECFailure.
+        ssl::_SECStatus_SECWouldBlock
+    }
+
+    unsafe extern "C" fn alert_sent_cb(
+        fd: *const ssl::PRFileDesc,
+        arg: *mut c_void,
+        alert: *const ssl::SSLAlert,
+    ) {
+        let alert = alert.as_ref().unwrap();
+        if alert.level == 2 {
+            // Fatal alerts demand attention.
+            let p = arg as *mut Option<Alert>;
+            let st = p.as_mut().unwrap();
+            if st.is_none() {
+                *st = Some(alert.description);
+            } else {
+                qwarn!([format!("{:p}", fd)] "duplicate alert {}", alert.description);
+            }
+        }
+    }
+
+    // TODO(mt) move to time.rs.
+    unsafe extern "C" fn time_func(arg: *mut c_void) -> PRTime {
+        let p = arg as *mut PRTime as *const PRTime;
+        *p.as_ref().unwrap()
+    }
+
+    // Ready this for connecting.
+    fn ready(&mut self, is_server: bool) -> Res<()> {
+        secstatus_to_res(unsafe {
+            ssl::SSL_AuthCertificateHook(
+                self.fd,
+                Some(Self::auth_complete_hook),
+                &mut *self.auth_required as *mut bool as *mut c_void,
+            )
+        })?;
+
+        secstatus_to_res(unsafe {
+            ssl::SSL_AlertSentCallback(
+                self.fd,
+                Some(Self::alert_sent_cb),
+                &mut *self.alert as *mut Option<Alert> as *mut c_void,
+            )
+        })?;
+
+        // TODO(mt) move to time.rs so we can remove PRTime definition from nss_ssl bindings.
+        unsafe {
+            ssl::SSL_SetTimeFunc(
+                self.fd,
+                Some(Self::time_func),
+                &mut *self.now as *mut PRTime as *mut c_void,
+            )
+        }?;
+
+        self.configure()?;
+        secstatus_to_res(unsafe { ssl::SSL_ResetHandshake(self.fd, is_server as ssl::PRBool) })
+    }
+
+    /// Default configuration.
+    fn configure(&mut self) -> Res<()> {
+        self.set_version_range(TLS_VERSION_1_3, TLS_VERSION_1_3)?;
+        self.set_option(ssl::Opt::Locking, false)?;
+        self.set_option(ssl::Opt::Tickets, false)?;
+        self.set_option(ssl::Opt::OcspStapling, true)?;
+        Ok(())
+    }
+
+    pub fn set_version_range(&mut self, min: Version, max: Version) -> Res<()> {
+        let range = ssl::SSLVersionRange {
+            min: min as ssl::PRUint16,
+            max: max as ssl::PRUint16,
+        };
+        secstatus_to_res(unsafe { ssl::SSL_VersionRangeSet(self.fd, &range) })
+    }
+
+    pub fn enable_ciphers(&mut self, ciphers: &[Cipher]) -> Res<()> {
+        let all_ciphers = unsafe { ssl::SSL_GetImplementedCiphers() };
+        let cipher_count = unsafe { ssl::SSL_GetNumImplementedCiphers() } as usize;
+        for i in 0..cipher_count {
+            let p = all_ciphers.wrapping_add(i);
+            secstatus_to_res(unsafe {
+                ssl::SSL_CipherPrefSet(self.fd, i32::from(*p), false as ssl::PRBool)
+            })?;
+        }
+
+        for c in ciphers {
+            secstatus_to_res(unsafe {
+                ssl::SSL_CipherPrefSet(self.fd, i32::from(*c), true as ssl::PRBool)
+            })?;
+        }
+        Ok(())
+    }
+
+    pub fn set_groups(&mut self, groups: &[Group]) -> Res<()> {
+        // SSLNamedGroup is a different size to Group, so copy one by one.
+        let group_vec: Vec<_> = groups
+            .iter()
+            .map(|&g| ssl::SSLNamedGroup::Type::from(g))
+            .collect();
+
+        let ptr = group_vec.as_slice().as_ptr();
+        secstatus_to_res(unsafe {
+            ssl::SSL_NamedGroupConfig(self.fd, ptr, c_uint::try_from(group_vec.len())?)
+        })
+    }
+
+    /// Set TLS options.
+    pub fn set_option(&mut self, opt: ssl::Opt, value: bool) -> Res<()> {
+        secstatus_to_res(unsafe {
+            ssl::SSL_OptionSet(self.fd, opt.as_int(), opt.map_enabled(value))
+        })
+    }
+
+    /// Enable 0-RTT.
+    pub fn enable_0rtt(&mut self) -> Res<()> {
+        self.set_option(ssl::Opt::EarlyData, true)
+    }
+
+    /// Disable the EndOfEarlyData message.
+    pub fn disable_end_of_early_data(&mut self) {
+        self.no_eoed = true;
+    }
+
+    /// set_alpn sets a list of preferred protocols, starting with the most preferred.
+    /// Though ALPN [RFC7301] permits octet sequences, this only allows for UTF-8-encoded
+    /// strings.
+    ///
+    /// This asserts if no items are provided, or if any individual item is longer than
+    /// 255 octets in length.
+    pub fn set_alpn(&mut self, protocols: &[impl AsRef<str>]) -> Res<()> {
+        // Validate and set length.
+        let mut encoded_len = protocols.len();
+        for v in protocols {
+            assert!(v.as_ref().len() < 256);
+            encoded_len += v.as_ref().len();
+        }
+
+        // Prepare to encode.
+        let mut encoded = Vec::with_capacity(encoded_len);
+        let mut add = |v: &str| {
+            if let Ok(s) = u8::try_from(v.len()) {
+                encoded.push(s);
+                encoded.extend_from_slice(v.as_bytes());
+            }
+        };
+
+        // NSS inherited an idiosyncratic API as a result of having implemented NPN
+        // before ALPN.  For that reason, we need to put the "best" option last.
+        let (first, rest) = protocols
+            .split_first()
+            .expect("at least one ALPN value needed");
+        for v in rest {
+            add(v.as_ref());
+        }
+        add(first.as_ref());
+        assert_eq!(encoded_len, encoded.len());
+
+        // Now give the result to NSS.
+        secstatus_to_res(unsafe {
+            ssl::SSL_SetNextProtoNego(
+                self.fd,
+                encoded.as_slice().as_ptr(),
+                c_uint::try_from(encoded.len())?,
+            )
+        })
+    }
+
+    /// Install an extension handler.
+    ///
+    /// This can be called multiple times with different values for `ext`.  The handler is provided as
+    /// Rc<RefCell<>> so that the caller is able to hold a reference to the handler and later access any
+    /// state that it accumulates.
+    pub fn extension_handler(
+        &mut self,
+        ext: Extension,
+        handler: Rc<RefCell<dyn ExtensionHandler>>,
+    ) -> Res<()> {
+        let tracker = unsafe { ExtensionTracker::new(self.fd, ext, handler) }?;
+        self.extension_handlers.push(tracker);
+        Ok(())
+    }
+
+    // This function tracks whether handshake() or handshake_raw() was used
+    // and prevents the other from being used.
+    fn set_raw(&mut self, r: bool) -> Res<()> {
+        if self.raw.is_none() {
+            self.secrets.register(self.fd)?;
+            self.raw = Some(r);
+            Ok(())
+        } else if self.raw.unwrap() == r {
+            Ok(())
+        } else {
+            Err(Error::MixedHandshakeMethod)
+        }
+    }
+
+    /// Get information about the connection.
+    /// This includes the version, ciphersuite, and ALPN.
+    ///
+    /// Calling this function returns None until the connection is complete.
+    pub fn info(&self) -> Option<&SecretAgentInfo> {
+        match self.state {
+            HandshakeState::Complete(ref info) => Some(info),
+            _ => None,
+        }
+    }
+
+    /// Get any preliminary information about the status of the connection.
+    ///
+    /// This includes whether 0-RTT was accepted and any information related to that.
+    /// Calling this function collects all the relevant information.
+    pub fn preinfo(&self) -> Res<SecretAgentPreInfo> {
+        SecretAgentPreInfo::new(self.fd)
+    }
+
+    /// Get the peer's certificate chain.
+    pub fn peer_certificate(&self) -> Option<CertificateInfo> {
+        CertificateInfo::new(self.fd)
+    }
+
+    /// Return any fatal alert that the TLS stack might have sent.
+    pub fn alert(&self) -> Option<&Alert> {
+        (&*self.alert).as_ref()
+    }
+
+    /// Call this function to mark the peer as authenticated.
+    /// Only call this function if handshake/handshake_raw returns
+    /// HandshakeState::AuthenticationPending, or it will panic.
+    pub fn authenticated(&mut self, status: AuthenticationStatus) {
+        assert_eq!(self.state, HandshakeState::AuthenticationPending);
+        *self.auth_required = false;
+        self.state = HandshakeState::Authenticated(status.into());
+    }
+
+    fn capture_error<T>(&mut self, res: Res<T>) -> Res<T> {
+        if let Err(e) = &res {
+            qwarn!([self] "error: {:?}", e);
+            self.state = HandshakeState::Failed(e.clone());
+        }
+        res
+    }
+
+    fn update_state(&mut self, res: Res<()>) -> Res<()> {
+        self.state = if is_blocked(&res) {
+            if *self.auth_required {
+                HandshakeState::AuthenticationPending
+            } else {
+                HandshakeState::InProgress
+            }
+        } else {
+            self.capture_error(res)?;
+            let info = self.capture_error(SecretAgentInfo::new(self.fd))?;
+            HandshakeState::Complete(info)
+        };
+        qinfo!([self] "state -> {:?}", self.state);
+        Ok(())
+    }
+
+    // Drive the TLS handshake, taking bytes from @input and putting
+    // any bytes necessary into @output.
+    // This takes the current time as @now.
+    // On success a tuple of a HandshakeState and usize indicate whether the handshake
+    // is complete and how many bytes were written to @output, respectively.
+    // If the state is HandshakeState::AuthenticationPending, then ONLY call this
+    // function if you want to proceed, because this will mark the certificate as OK.
+    pub fn handshake(&mut self, now: Instant, input: &[u8]) -> Res<Vec<u8>> {
+        *self.now = Time::from(now).try_into()?;
+        self.set_raw(false)?;
+
+        let rv = {
+            // Within this scope, _h maintains a mutable reference to self.io.
+            let _h = self.io.wrap(input);
+            match self.state {
+                HandshakeState::Authenticated(ref err) => unsafe {
+                    ssl::SSL_AuthCertificateComplete(self.fd, *err)
+                },
+                _ => unsafe { ssl::SSL_ForceHandshake(self.fd) },
+            }
+        };
+        // Take before updating state so that we leave the output buffer empty
+        // even if there is an error.
+        let output = self.io.take_output();
+        self.update_state(secstatus_to_res(rv))?;
+        Ok(output)
+    }
+
+    /// Setup to receive records for raw handshake functions.
+    fn setup_raw(&mut self) -> Res<Box<RecordList>> {
+        self.set_raw(true)?;
+        self.capture_error(RecordList::setup(self.fd))
+    }
+
+    fn inject_eoed(&mut self) -> Res<()> {
+        // EndOfEarlyData is as follows:
+        // struct {
+        //    HandshakeType msg_type = end_of_early_data(5);
+        //    uint24 length = 0;
+        // };
+        const END_OF_EARLY_DATA: &[u8] = &[5, 0, 0, 0];
+
+        if self.no_eoed {
+            let mut read_epoch: u16 = 0;
+            unsafe { ssl::SSL_GetCurrentEpoch(self.fd, &mut read_epoch, null_mut()) }?;
+            if read_epoch == 1 {
+                // It's waiting for EndOfEarlyData, so feed one in.
+                // Note that this is the test that ensures that we only do this for the server.
+                let eoed = Record::new(1, 22, END_OF_EARLY_DATA);
+                self.capture_error(eoed.write(self.fd))?;
+            }
+        }
+        self.no_eoed = false;
+        Ok(())
+    }
+
+    // Drive the TLS handshake, but get the raw content of records, not
+    // protected records as bytes. This function is incompatible with
+    // handshake(); use either this or handshake() exclusively.
+    //
+    // Ideally, this only includes records from the current epoch.
+    // If you send data from multiple epochs, you might end up being sad.
+    pub fn handshake_raw(&mut self, now: Instant, input: Option<Record>) -> Res<RecordList> {
+        *self.now = Time::from(now).try_into()?;
+        let mut records = self.setup_raw()?;
+
+        // Fire off any authentication we might need to complete.
+        if let HandshakeState::Authenticated(ref err) = self.state {
+            let result =
+                secstatus_to_res(unsafe { ssl::SSL_AuthCertificateComplete(self.fd, *err) });
+            qdebug!([self] "SSL_AuthCertificateComplete: {:?}", result);
+            // This should return SECSuccess, so don't use update_state().
+            self.capture_error(result)?;
+        }
+
+        // Feed in any records.
+        if let Some(rec) = input {
+            if rec.epoch == 2 {
+                self.inject_eoed()?;
+            }
+            self.capture_error(rec.write(self.fd))?;
+        }
+
+        // Drive the handshake once more.
+        let rv = secstatus_to_res(unsafe { ssl::SSL_ForceHandshake(self.fd) });
+        self.update_state(rv)?;
+
+        if self.no_eoed {
+            records.remove_eoed();
+        }
+
+        Ok(*records)
+    }
+
+    // State returns the status of the handshake.
+    pub fn state(&self) -> &HandshakeState {
+        &self.state
+    }
+
+    pub fn read_secret(&self, epoch: Epoch) -> Option<&p11::SymKey> {
+        self.secrets.read().get(epoch)
+    }
+
+    pub fn write_secret(&self, epoch: Epoch) -> Option<&p11::SymKey> {
+        self.secrets.write().get(epoch)
+    }
+}
+
+impl ::std::fmt::Display for SecretAgent {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+        write!(f, "Agent {:p}", self.fd)
+    }
+}
+
+/// A TLS Client.
+#[derive(Debug)]
+pub struct Client {
+    agent: SecretAgent,
+
+    /// Records the last resumption token.
+    resumption: Box<Option<Vec<u8>>>,
+}
+
+impl Client {
+    pub fn new(server_name: &str) -> Res<Self> {
+        let mut agent = SecretAgent::new()?;
+        let url = CString::new(server_name)?;
+        secstatus_to_res(unsafe { ssl::SSL_SetURL(agent.fd, url.as_ptr()) })?;
+        agent.ready(false)?;
+        let mut client = Self {
+            agent,
+            resumption: Box::new(None),
+        };
+        client.ready()?;
+        Ok(client)
+    }
+
+    unsafe extern "C" fn resumption_token_cb(
+        fd: *mut ssl::PRFileDesc,
+        token: *const u8,
+        len: c_uint,
+        arg: *mut c_void,
+    ) -> ssl::SECStatus {
+        let resumption_ptr = arg as *mut Option<Vec<u8>>;
+        let resumption = resumption_ptr.as_mut().unwrap();
+        let mut v = Vec::with_capacity(len as usize);
+        v.extend_from_slice(std::slice::from_raw_parts(token, len as usize));
+        qdebug!([format!("{:p}", fd)] "Got resumption token");
+        *resumption = Some(v);
+        ssl::SECSuccess
+    }
+
+    fn ready(&mut self) -> Res<()> {
+        unsafe {
+            ssl::SSL_SetResumptionTokenCallback(
+                self.fd,
+                Some(Self::resumption_token_cb),
+                &mut *self.resumption as *mut Option<Vec<u8>> as *mut c_void,
+            )
+        }
+    }
+
+    /// Return the resumption token.
+    pub fn resumption_token(&self) -> Option<&Vec<u8>> {
+        (*self.resumption).as_ref()
+    }
+
+    /// Enable resumption, using a token previously provided.
+    pub fn set_resumption_token(&mut self, token: &[u8]) -> Res<()> {
+        unsafe {
+            ssl::SSL_SetResumptionToken(
+                self.agent.fd,
+                token.as_ptr(),
+                c_uint::try_from(token.len())?,
+            )
+        }
+    }
+}
+
+impl Deref for Client {
+    type Target = SecretAgent;
+    fn deref(&self) -> &SecretAgent {
+        &self.agent
+    }
+}
+
+impl DerefMut for Client {
+    fn deref_mut(&mut self) -> &mut SecretAgent {
+        &mut self.agent
+    }
+}
+
+/// `ZeroRttCheckResult` encapsulates the options for handling a `ClientHello`.
+#[derive(Clone, Debug, PartialEq)]
+pub enum ZeroRttCheckResult {
+    /// Accept 0-RTT; the default.
+    Accept,
+    /// Reject 0-RTT, but continue the handshake normally.
+    Reject,
+    /// Send HelloRetryRequest (probably not needed for QUIC).
+    HelloRetryRequest(Vec<u8>),
+    /// Fail the handshake.
+    Fail,
+}
+
+/// A `ZeroRttChecker` is used by the agent to validate the application token (as provided by `send_ticket`)
+pub trait ZeroRttChecker: std::fmt::Debug {
+    fn check(&self, token: &[u8]) -> ZeroRttCheckResult;
+}
+
+#[derive(Debug)]
+struct ZeroRttCheckState {
+    fd: *mut ssl::PRFileDesc,
+    checker: Box<dyn ZeroRttChecker>,
+}
+
+impl ZeroRttCheckState {
+    pub fn new(fd: *mut ssl::PRFileDesc, checker: Box<dyn ZeroRttChecker>) -> Box<Self> {
+        Box::new(Self { fd, checker })
+    }
+}
+
+#[derive(Debug)]
+pub struct Server {
+    agent: SecretAgent,
+    /// This holds the HRR callback context.
+    zero_rtt_check: Option<Box<ZeroRttCheckState>>,
+}
+
+impl Server {
+    pub fn new(certificates: &[impl AsRef<str>]) -> Res<Self> {
+        let mut agent = SecretAgent::new()?;
+
+        for n in certificates {
+            let c = CString::new(n.as_ref())?;
+            let cert = match NonNull::new(unsafe {
+                p11::PK11_FindCertFromNickname(c.as_ptr(), null_mut())
+            }) {
+                None => return Err(Error::CertificateLoading),
+                Some(ptr) => p11::Certificate::new(ptr),
+            };
+            let key = match NonNull::new(unsafe {
+                p11::PK11_FindKeyByAnyCert(*cert.deref(), null_mut())
+            }) {
+                None => return Err(Error::CertificateLoading),
+                Some(ptr) => p11::PrivateKey::new(ptr),
+            };
+            secstatus_to_res(unsafe {
+                ssl::SSL_ConfigServerCert(agent.fd, *cert.deref(), *key.deref(), null(), 0)
+            })?;
+        }
+
+        agent.ready(true)?;
+        Ok(Self {
+            agent,
+            zero_rtt_check: None,
+        })
+    }
+
+    unsafe extern "C" fn hello_retry_cb(
+        first_hello: PRBool,
+        client_token: *const u8,
+        client_token_len: c_uint,
+        retry_token: *mut u8,
+        retry_token_len: *mut c_uint,
+        retry_token_max: c_uint,
+        arg: *mut c_void,
+    ) -> ssl::SSLHelloRetryRequestAction::Type {
+        if first_hello == 0 {
+            // On the second ClientHello after HelloRetryRequest, skip checks.
+            return ssl::SSLHelloRetryRequestAction::ssl_hello_retry_accept;
+        }
+
+        let p = arg as *mut ZeroRttCheckState;
+        let check_state = p.as_mut().unwrap();
+        let token = if client_token.is_null() {
+            &[]
+        } else {
+            std::slice::from_raw_parts(client_token, client_token_len as usize)
+        };
+        match check_state.checker.check(token) {
+            ZeroRttCheckResult::Accept => ssl::SSLHelloRetryRequestAction::ssl_hello_retry_accept,
+            ZeroRttCheckResult::Fail => ssl::SSLHelloRetryRequestAction::ssl_hello_retry_fail,
+            ZeroRttCheckResult::Reject => {
+                ssl::SSLHelloRetryRequestAction::ssl_hello_retry_reject_0rtt
+            }
+            ZeroRttCheckResult::HelloRetryRequest(tok) => {
+                // Don't bother propagating errors from this, because it should be caught in testing.
+                assert!(tok.len() <= usize::try_from(retry_token_max).unwrap());
+                let slc = std::slice::from_raw_parts_mut(retry_token, tok.len());
+                slc.copy_from_slice(&tok);
+                *retry_token_len = c_uint::try_from(tok.len()).expect("token was way too big");
+                ssl::SSLHelloRetryRequestAction::ssl_hello_retry_request
+            }
+        }
+    }
+
+    /// Enable 0-RTT.  This shadows the function of the same name that can be accessed
+    /// via the Deref implementation on Server.
+    pub fn enable_0rtt(
+        &mut self,
+        anti_replay: &AntiReplay,
+        max_early_data: u32,
+        checker: Box<dyn ZeroRttChecker>,
+    ) -> Res<()> {
+        let mut check_state = ZeroRttCheckState::new(self.agent.fd, checker);
+        let arg = &mut *check_state as *mut ZeroRttCheckState as *mut c_void;
+        unsafe {
+            ssl::SSL_HelloRetryRequestCallback(self.agent.fd, Some(Self::hello_retry_cb), arg)
+        }?;
+        unsafe { ssl::SSL_SetMaxEarlyDataSize(self.agent.fd, max_early_data) }?;
+        self.zero_rtt_check = Some(check_state);
+        self.agent.enable_0rtt()?;
+        anti_replay.config_socket(self.fd)?;
+        Ok(())
+    }
+
+    /// Send a session ticket to the client.
+    /// This adds |extra| application-specific content into that ticket.
+    /// The records that are sent are captured and returned.
+    pub fn send_ticket(&mut self, now: Instant, extra: &[u8]) -> Res<RecordList> {
+        *self.agent.now = Time::from(now).try_into()?;
+        let records = self.setup_raw()?;
+
+        unsafe {
+            ssl::SSL_SendSessionTicket(self.fd, extra.as_ptr(), c_uint::try_from(extra.len())?)
+        }?;
+
+        Ok(*records)
+    }
+}
+
+impl Deref for Server {
+    type Target = SecretAgent;
+    fn deref(&self) -> &SecretAgent {
+        &self.agent
+    }
+}
+
+impl DerefMut for Server {
+    fn deref_mut(&mut self) -> &mut SecretAgent {
+        &mut self.agent
+    }
+}
+
+/// A generic container for Client or Server.
+#[derive(Debug)]
+pub enum Agent {
+    Client(crate::agent::Client),
+    Server(crate::agent::Server),
+}
+
+impl Deref for Agent {
+    type Target = SecretAgent;
+    fn deref(&self) -> &SecretAgent {
+        match self {
+            Agent::Client(c) => &*c,
+            Agent::Server(s) => &*s,
+        }
+    }
+}
+
+impl DerefMut for Agent {
+    fn deref_mut(&mut self) -> &mut SecretAgent {
+        match self {
+            Agent::Client(c) => c.deref_mut(),
+            Agent::Server(s) => s.deref_mut(),
+        }
+    }
+}
+
+impl From<Client> for Agent {
+    fn from(c: Client) -> Self {
+        Agent::Client(c)
+    }
+}
+
+impl From<Server> for Agent {
+    fn from(s: Server) -> Self {
+        Agent::Server(s)
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/agentio.rs
@@ -0,0 +1,395 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::constants::*;
+use crate::err::{nspr, Error, PR_SetError, Res};
+use crate::prio;
+use crate::ssl;
+
+use neqo_common::{hex, qtrace};
+use std::cmp::min;
+use std::convert::{TryFrom, TryInto};
+use std::fmt;
+use std::mem;
+use std::ops::Deref;
+use std::os::raw::{c_uint, c_void};
+use std::ptr::{null, null_mut};
+use std::vec::Vec;
+
+// Alias common types.
+type PrFd = *mut prio::PRFileDesc;
+type PrStatus = prio::PRStatus::Type;
+const PR_SUCCESS: PrStatus = prio::PRStatus::PR_SUCCESS;
+const PR_FAILURE: PrStatus = prio::PRStatus::PR_FAILURE;
+
+// This holds the length of the slice, not the slice itself.
+#[derive(Default, Debug)]
+struct RecordLength {
+    epoch: Epoch,
+    ct: ssl::SSLContentType::Type,
+    len: usize,
+}
+
+/// A slice of the output.
+#[derive(Default)]
+pub struct Record {
+    pub epoch: Epoch,
+    pub ct: ssl::SSLContentType::Type,
+    pub data: Vec<u8>,
+}
+
+impl Record {
+    pub fn new(epoch: Epoch, ct: ssl::SSLContentType::Type, data: &[u8]) -> Self {
+        Self {
+            epoch,
+            ct,
+            data: data.to_vec(),
+        }
+    }
+
+    // Shoves this record into the socket, returns true if blocked.
+    pub(crate) fn write(self, fd: *mut ssl::PRFileDesc) -> Res<()> {
+        qtrace!("write {:?}", self);
+        unsafe {
+            ssl::SSL_RecordLayerData(
+                fd,
+                self.epoch,
+                self.ct,
+                self.data.as_ptr(),
+                c_uint::try_from(self.data.len())?,
+            )
+        }
+    }
+}
+
+impl fmt::Debug for Record {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(
+            f,
+            "Record {:?}:{:?} {}",
+            self.epoch,
+            self.ct,
+            hex(&self.data[..])
+        )
+    }
+}
+
+#[derive(Debug, Default)]
+pub struct RecordList {
+    records: Vec<Record>,
+}
+
+impl RecordList {
+    fn append(&mut self, epoch: Epoch, ct: ssl::SSLContentType::Type, data: &[u8]) {
+        self.records.push(Record::new(epoch, ct, data));
+    }
+
+    /// Filter out EndOfEarlyData messages.
+    pub fn remove_eoed(&mut self) {
+        self.records.retain(|rec| rec.epoch != 1);
+    }
+
+    unsafe extern "C" fn ingest(
+        _fd: *mut ssl::PRFileDesc,
+        epoch: ssl::PRUint16,
+        ct: ssl::SSLContentType::Type,
+        data: *const ssl::PRUint8,
+        len: c_uint,
+        arg: *mut c_void,
+    ) -> ssl::SECStatus {
+        let a = arg as *mut RecordList;
+        let records = a.as_mut().unwrap();
+
+        let slice = std::slice::from_raw_parts(data, len as usize);
+        records.append(epoch, ct, slice);
+        ssl::SECSuccess
+    }
+
+    /// Create a new record list.
+    pub(crate) fn setup(fd: *mut ssl::PRFileDesc) -> Res<Box<RecordList>> {
+        let mut records = Box::new(RecordList::default());
+        let records_ptr = &mut *records as *mut RecordList as *mut c_void;
+        unsafe { ssl::SSL_RecordLayerWriteCallback(fd, Some(RecordList::ingest), records_ptr) }?;
+        Ok(records)
+    }
+}
+
+impl Deref for RecordList {
+    type Target = Vec<Record>;
+    fn deref(&self) -> &Vec<Record> {
+        &self.records
+    }
+}
+
+pub struct RecordListIter(std::vec::IntoIter<Record>);
+
+impl Iterator for RecordListIter {
+    type Item = Record;
+    fn next(&mut self) -> Option<Self::Item> {
+        self.0.next()
+    }
+}
+
+impl IntoIterator for RecordList {
+    type Item = Record;
+    type IntoIter = RecordListIter;
+    fn into_iter(self) -> Self::IntoIter {
+        RecordListIter(self.records.into_iter())
+    }
+}
+
+pub struct AgentIoInputContext<'a> {
+    input: &'a mut AgentIoInput,
+}
+
+impl<'a> Drop for AgentIoInputContext<'a> {
+    fn drop(&mut self) {
+        self.input.reset();
+    }
+}
+
+#[derive(Debug)]
+struct AgentIoInput {
+    // input is data that is read by TLS.
+    input: *const u8,
+    // input_available is how much data is left for reading.
+    available: usize,
+}
+
+impl AgentIoInput {
+    fn wrap<'a: 'c, 'b: 'c, 'c>(&'a mut self, input: &'b [u8]) -> AgentIoInputContext<'c> {
+        assert!(self.input.is_null());
+        self.input = input.as_ptr();
+        self.available = input.len();
+        qtrace!("AgentIoInput wrap {:p}", self.input);
+        AgentIoInputContext { input: self }
+    }
+
+    // Take the data provided as input and provide it to the TLS stack.
+    fn read_input(&mut self, buf: *mut u8, count: usize) -> Res<usize> {
+        let amount = min(self.available, count);
+        if amount == 0 {
+            unsafe { PR_SetError(nspr::PR_WOULD_BLOCK_ERROR, 0) };
+            return Err(Error::NoDataAvailable);
+        }
+
+        let src = unsafe { std::slice::from_raw_parts(self.input, amount) };
+        qtrace!([self] "read {}", hex(src));
+        let dst = unsafe { std::slice::from_raw_parts_mut(buf, amount) };
+        dst.copy_from_slice(&src);
+        self.input = self.input.wrapping_add(amount);
+        self.available -= amount;
+        Ok(amount)
+    }
+
+    fn reset(&mut self) {
+        qtrace!([self] "reset");
+        self.input = null();
+        self.available = 0;
+    }
+}
+
+impl ::std::fmt::Display for AgentIoInput {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+        write!(f, "AgentIoInput {:p}", self.input)
+    }
+}
+
+#[derive(Debug)]
+pub struct AgentIo {
+    // input collects the input we might provide to TLS.
+    input: AgentIoInput,
+
+    // output contains data that is written by TLS.
+    output: Vec<u8>,
+}
+
+impl AgentIo {
+    pub fn new() -> Self {
+        Self {
+            input: AgentIoInput {
+                input: null(),
+                available: 0,
+            },
+            output: Vec::new(),
+        }
+    }
+
+    unsafe fn borrow(fd: &mut PrFd) -> &mut Self {
+        #[allow(clippy::cast_ptr_alignment)]
+        let io = (**fd).secret as *mut Self;
+        io.as_mut().unwrap()
+    }
+
+    pub fn wrap<'a: 'c, 'b: 'c, 'c>(&'a mut self, input: &'b [u8]) -> AgentIoInputContext<'c> {
+        assert_eq!(self.output.len(), 0);
+        self.input.wrap(input)
+    }
+
+    // Stage output from TLS into the output buffer.
+    fn save_output(&mut self, buf: *const u8, count: usize) {
+        let slice = unsafe { std::slice::from_raw_parts(buf, count) };
+        qtrace!([self] "save output {}", hex(slice));
+        self.output.extend_from_slice(slice);
+    }
+
+    pub fn take_output(&mut self) -> Vec<u8> {
+        qtrace!([self] "take output");
+        mem::replace(&mut self.output, Vec::new())
+    }
+}
+
+impl ::std::fmt::Display for AgentIo {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+        write!(f, "AgentIo")
+    }
+}
+
+unsafe extern "C" fn agent_close(fd: PrFd) -> PrStatus {
+    if let Some(dtor) = (*fd).dtor {
+        dtor(fd);
+    }
+    (*fd).secret = null_mut();
+    PR_SUCCESS
+}
+
+unsafe extern "C" fn agent_read(mut fd: PrFd, buf: *mut c_void, amount: prio::PRInt32) -> PrStatus {
+    let io = AgentIo::borrow(&mut fd);
+    if let Ok(a) = usize::try_from(amount) {
+        match io.input.read_input(buf as *mut u8, a) {
+            Ok(_) => PR_SUCCESS,
+            Err(_) => PR_FAILURE,
+        }
+    } else {
+        PR_FAILURE
+    }
+}
+
+unsafe extern "C" fn agent_recv(
+    mut fd: PrFd,
+    buf: *mut c_void,
+    amount: prio::PRInt32,
+    flags: prio::PRIntn,
+    _timeout: prio::PRIntervalTime,
+) -> prio::PRInt32 {
+    let io = AgentIo::borrow(&mut fd);
+    if flags != 0 {
+        return PR_FAILURE;
+    }
+    if let Ok(a) = usize::try_from(amount) {
+        match io.input.read_input(buf as *mut u8, a) {
+            Ok(v) => prio::PRInt32::try_from(v).unwrap_or(PR_FAILURE),
+            Err(_) => PR_FAILURE,
+        }
+    } else {
+        PR_FAILURE
+    }
+}
+
+unsafe extern "C" fn agent_write(
+    mut fd: PrFd,
+    buf: *const c_void,
+    amount: prio::PRInt32,
+) -> PrStatus {
+    let io = AgentIo::borrow(&mut fd);
+    if let Ok(a) = usize::try_from(amount) {
+        io.save_output(buf as *const u8, a);
+        amount
+    } else {
+        PR_FAILURE
+    }
+}
+
+unsafe extern "C" fn agent_send(
+    mut fd: PrFd,
+    buf: *const c_void,
+    amount: prio::PRInt32,
+    flags: prio::PRIntn,
+    _timeout: prio::PRIntervalTime,
+) -> prio::PRInt32 {
+    let io = AgentIo::borrow(&mut fd);
+
+    if flags != 0 {
+        return PR_FAILURE;
+    }
+    if let Ok(a) = usize::try_from(amount) {
+        io.save_output(buf as *const u8, a);
+        amount
+    } else {
+        PR_FAILURE
+    }
+}
+
+unsafe extern "C" fn agent_available(mut fd: PrFd) -> prio::PRInt32 {
+    let io = AgentIo::borrow(&mut fd);
+    io.input.available.try_into().unwrap_or(PR_FAILURE)
+}
+
+unsafe extern "C" fn agent_available64(mut fd: PrFd) -> prio::PRInt64 {
+    let io = AgentIo::borrow(&mut fd);
+    io.input
+        .available
+        .try_into()
+        .unwrap_or_else(|_| PR_FAILURE.into())
+}
+
+#[allow(clippy::cast_possible_truncation)]
+unsafe extern "C" fn agent_getname(_fd: PrFd, addr: *mut prio::PRNetAddr) -> PrStatus {
+    let a = addr.as_mut().unwrap();
+    // Cast is safe because prio::PR_AF_INET is 2
+    a.inet.family = prio::PR_AF_INET as prio::PRUint16;
+    a.inet.port = 0;
+    a.inet.ip = 0;
+    PR_SUCCESS
+}
+
+unsafe extern "C" fn agent_getsockopt(_fd: PrFd, opt: *mut prio::PRSocketOptionData) -> PrStatus {
+    let o = opt.as_mut().unwrap();
+    if o.option == prio::PRSockOption::PR_SockOpt_Nonblocking {
+        o.value.non_blocking = 1;
+        return PR_SUCCESS;
+    }
+    PR_FAILURE
+}
+
+pub const METHODS: &prio::PRIOMethods = &prio::PRIOMethods {
+    file_type: prio::PRDescType::PR_DESC_LAYERED,
+    close: Some(agent_close),
+    read: Some(agent_read),
+    write: Some(agent_write),
+    available: Some(agent_available),
+    available64: Some(agent_available64),
+    fsync: None,
+    seek: None,
+    seek64: None,
+    fileInfo: None,
+    fileInfo64: None,
+    writev: None,
+    connect: None,
+    accept: None,
+    bind: None,
+    listen: None,
+    shutdown: None,
+    recv: Some(agent_recv),
+    send: Some(agent_send),
+    recvfrom: None,
+    sendto: None,
+    poll: None,
+    acceptread: None,
+    transmitfile: None,
+    getsockname: Some(agent_getname),
+    getpeername: Some(agent_getname),
+    reserved_fn_6: None,
+    reserved_fn_5: None,
+    getsocketoption: Some(agent_getsockopt),
+    setsocketoption: None,
+    sendfile: None,
+    connectcontinue: None,
+    reserved_fn_3: None,
+    reserved_fn_2: None,
+    reserved_fn_1: None,
+    reserved_fn_0: None,
+};
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/auth.rs
@@ -0,0 +1,114 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::err::{mozpkix, sec, ssl, PRErrorCode};
+
+/// The outcome of authentication.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum AuthenticationStatus {
+    Ok,
+    CaInvalid,
+    CaNotV3,
+    CertAlgorithmDisabled,
+    CertExpired,
+    CertInvalidTime,
+    CertIsCa,
+    CertKeyUsage,
+    CertMitm,
+    CertNotYetValid,
+    CertRevoked,
+    CertSelfSigned,
+    CertSubjectInvalid,
+    CertUntrusted,
+    CertWeakKey,
+    IssuerEmptyName,
+    IssuerExpired,
+    IssuerNotYetValid,
+    IssuerUnknown,
+    IssuerUntrusted,
+    PolicyRejection,
+    Unknown,
+}
+
+impl Into<PRErrorCode> for AuthenticationStatus {
+    fn into(self) -> PRErrorCode {
+        match self {
+            AuthenticationStatus::Ok => 0,
+            AuthenticationStatus::CaInvalid => sec::SEC_ERROR_CA_CERT_INVALID,
+            AuthenticationStatus::CaNotV3 => mozpkix::MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA,
+            AuthenticationStatus::CertAlgorithmDisabled => {
+                sec::SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
+            }
+            AuthenticationStatus::CertExpired => sec::SEC_ERROR_EXPIRED_CERTIFICATE,
+            AuthenticationStatus::CertInvalidTime => sec::SEC_ERROR_INVALID_TIME,
+            AuthenticationStatus::CertIsCa => {
+                mozpkix::MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY
+            }
+            AuthenticationStatus::CertKeyUsage => sec::SEC_ERROR_INADEQUATE_KEY_USAGE,
+            AuthenticationStatus::CertMitm => mozpkix::MOZILLA_PKIX_ERROR_MITM_DETECTED,
+            AuthenticationStatus::CertNotYetValid => {
+                mozpkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE
+            }
+            AuthenticationStatus::CertRevoked => sec::SEC_ERROR_REVOKED_CERTIFICATE,
+            AuthenticationStatus::CertSelfSigned => mozpkix::MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT,
+            AuthenticationStatus::CertSubjectInvalid => ssl::SSL_ERROR_BAD_CERT_DOMAIN,
+            AuthenticationStatus::CertUntrusted => sec::SEC_ERROR_UNTRUSTED_CERT,
+            AuthenticationStatus::CertWeakKey => mozpkix::MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE,
+            AuthenticationStatus::IssuerEmptyName => mozpkix::MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME,
+            AuthenticationStatus::IssuerExpired => sec::SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE,
+            AuthenticationStatus::IssuerNotYetValid => {
+                mozpkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE
+            }
+            AuthenticationStatus::IssuerUnknown => sec::SEC_ERROR_UNKNOWN_ISSUER,
+            AuthenticationStatus::IssuerUntrusted => sec::SEC_ERROR_UNTRUSTED_ISSUER,
+            AuthenticationStatus::PolicyRejection => {
+                mozpkix::MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED
+            }
+            AuthenticationStatus::Unknown => sec::SEC_ERROR_LIBRARY_FAILURE,
+        }
+    }
+}
+
+// Note that this mapping should be removed after gecko eventually learns how to
+// map into the enumerated type.
+impl From<PRErrorCode> for AuthenticationStatus {
+    fn from(v: PRErrorCode) -> Self {
+        match v {
+            0 => AuthenticationStatus::Ok,
+            sec::SEC_ERROR_CA_CERT_INVALID => AuthenticationStatus::CaInvalid,
+            mozpkix::MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA => AuthenticationStatus::CaNotV3,
+            sec::SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED => {
+                AuthenticationStatus::CertAlgorithmDisabled
+            }
+            sec::SEC_ERROR_EXPIRED_CERTIFICATE => AuthenticationStatus::CertExpired,
+            sec::SEC_ERROR_INVALID_TIME => AuthenticationStatus::CertInvalidTime,
+            mozpkix::MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY => {
+                AuthenticationStatus::CertIsCa
+            }
+            sec::SEC_ERROR_INADEQUATE_KEY_USAGE => AuthenticationStatus::CertKeyUsage,
+            mozpkix::MOZILLA_PKIX_ERROR_MITM_DETECTED => AuthenticationStatus::CertMitm,
+            mozpkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE => {
+                AuthenticationStatus::CertNotYetValid
+            }
+            sec::SEC_ERROR_REVOKED_CERTIFICATE => AuthenticationStatus::CertRevoked,
+            mozpkix::MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT => AuthenticationStatus::CertSelfSigned,
+            ssl::SSL_ERROR_BAD_CERT_DOMAIN => AuthenticationStatus::CertSubjectInvalid,
+            sec::SEC_ERROR_UNTRUSTED_CERT => AuthenticationStatus::CertUntrusted,
+            mozpkix::MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE => AuthenticationStatus::CertWeakKey,
+            mozpkix::MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME => AuthenticationStatus::IssuerEmptyName,
+            sec::SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE => AuthenticationStatus::IssuerExpired,
+            mozpkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE => {
+                AuthenticationStatus::IssuerNotYetValid
+            }
+            sec::SEC_ERROR_UNKNOWN_ISSUER => AuthenticationStatus::IssuerUnknown,
+            sec::SEC_ERROR_UNTRUSTED_ISSUER => AuthenticationStatus::IssuerUntrusted,
+            mozpkix::MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED => {
+                AuthenticationStatus::PolicyRejection
+            }
+            _ => AuthenticationStatus::Unknown,
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/cert.rs
@@ -0,0 +1,126 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::err::secstatus_to_res;
+use crate::p11::{
+    CERTCertList, CERTCertListNode, CERT_GetCertificateDer, CertList, PRCList, SECItem,
+    SECItemArray, SECItemType,
+};
+use crate::ssl::{
+    PRFileDesc, SSL_PeerCertificateChain, SSL_PeerSignedCertTimestamps,
+    SSL_PeerStapledOCSPResponses,
+};
+use neqo_common::qerror;
+
+use std::convert::TryFrom;
+use std::ptr::{null_mut, NonNull};
+
+use std::slice;
+
+pub struct CertificateInfo {
+    certs: CertList,
+    cursor: *const CERTCertListNode,
+    /// stapled_ocsp_responses and signed_cert_timestamp are properties
+    /// associated with each of the certificates. Right now, NSS only
+    /// reports the value for the end-entity certificate (the first).
+    stapled_ocsp_responses: Option<Vec<Vec<u8>>>,
+    signed_cert_timestamp: Option<Vec<u8>>,
+}
+
+fn peer_certificate_chain(fd: *mut PRFileDesc) -> Option<(CertList, *const CERTCertListNode)> {
+    let chain = unsafe { SSL_PeerCertificateChain(fd) };
+    let certs = match NonNull::new(chain as *mut CERTCertList) {
+        Some(certs_ptr) => CertList::new(certs_ptr),
+        None => return None,
+    };
+    let cursor = CertificateInfo::head(&certs);
+    Some((certs, cursor))
+}
+
+// As explained in rfc6961, an OCSPResponseList can have at most
+// 2^24 items. Casting its length is therefore safe even on 32 bits targets.
+fn stapled_ocsp_responses(fd: *mut PRFileDesc) -> Option<Vec<Vec<u8>>> {
+    let ocsp_nss = unsafe { SSL_PeerStapledOCSPResponses(fd) };
+    match NonNull::new(ocsp_nss as *mut SECItemArray) {
+        Some(ocsp_ptr) => {
+            let mut ocsp_helper: Vec<Vec<u8>> = Vec::new();
+            let len = if let Ok(l) = isize::try_from(unsafe { ocsp_ptr.as_ref().len }) {
+                l
+            } else {
+                qerror!([format!("{:p}", fd)], "Received illegal OSCP length");
+                return None;
+            };
+            for idx in 0..len {
+                let itemp = unsafe { ocsp_ptr.as_ref().items.offset(idx) as *const SECItem };
+                let item = unsafe { slice::from_raw_parts((*itemp).data, (*itemp).len as usize) };
+                ocsp_helper.push(item.to_owned());
+            }
+            Some(ocsp_helper)
+        }
+        None => None,
+    }
+}
+
+fn signed_cert_timestamp(fd: *mut PRFileDesc) -> Option<Vec<u8>> {
+    let sct_nss = unsafe { SSL_PeerSignedCertTimestamps(fd) };
+    match NonNull::new(sct_nss as *mut SECItem) {
+        Some(sct_ptr) => {
+            let sct_slice = unsafe {
+                slice::from_raw_parts(sct_ptr.as_ref().data, sct_ptr.as_ref().len as usize)
+            };
+            Some(sct_slice.to_owned())
+        }
+        None => None,
+    }
+}
+
+impl CertificateInfo {
+    pub(crate) fn new(fd: *mut PRFileDesc) -> Option<Self> {
+        match peer_certificate_chain(fd) {
+            Some((certs, cursor)) => Some(Self {
+                certs,
+                cursor,
+                stapled_ocsp_responses: stapled_ocsp_responses(fd),
+                signed_cert_timestamp: signed_cert_timestamp(fd),
+            }),
+            None => None,
+        }
+    }
+
+    fn head(certs: &CertList) -> *const CERTCertListNode {
+        // Three stars: one for the reference, one for the wrapper, one to deference the pointer.
+        unsafe { &(***certs).list as *const PRCList as *const CERTCertListNode }
+    }
+}
+
+impl<'a> Iterator for &'a mut CertificateInfo {
+    type Item = &'a [u8];
+    fn next(&mut self) -> Option<&'a [u8]> {
+        self.cursor = unsafe { *self.cursor }.links.next as *const CERTCertListNode;
+        if self.cursor == CertificateInfo::head(&self.certs) {
+            return None;
+        }
+        let mut item = SECItem {
+            type_: SECItemType::siBuffer,
+            data: null_mut(),
+            len: 0,
+        };
+        let cert = unsafe { *self.cursor }.cert;
+        secstatus_to_res(unsafe { CERT_GetCertificateDer(cert, &mut item) })
+            .expect("getting DER from certificate should work");
+        Some(unsafe { std::slice::from_raw_parts(item.data, item.len as usize) })
+    }
+}
+
+impl CertificateInfo {
+    pub fn stapled_ocsp_responses(&mut self) -> &Option<Vec<Vec<u8>>> {
+        &self.stapled_ocsp_responses
+    }
+
+    pub fn signed_cert_timestamp(&mut self) -> &Option<Vec<u8>> {
+        &self.signed_cert_timestamp
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/constants.rs
@@ -0,0 +1,127 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![allow(dead_code)]
+
+use crate::ssl;
+
+// Ideally all of these would be enums, but size matters and we need to allow
+// for values outside of those that are defined here.
+
+pub type Alert = u8;
+pub type Epoch = u16;
+
+/// Rather than defining a type alias and a bunch of constants, which leads to a ton of repetition,
+/// use this macro.
+macro_rules! remap_enum {
+    { $t:ident: $s:ty { $( $n:ident = $v:path ),+ $(,)? } } => {
+        pub type $t = $s;
+        $( pub const $n: $t = $v as $t; )+
+    };
+    { $t:ident: $s:ty => $e:ident { $( $n:ident = $v:ident ),+ $(,)? } } => {
+        remap_enum!{ $t: $s { $( $n = $e::$v ),+ } }
+    };
+    { $t:ident: $s:ty => $p:ident::$e:ident { $( $n:ident = $v:ident ),+ $(,)? } } => {
+        remap_enum!{ $t: $s { $( $n = $p::$e::$v ),+ } }
+    };
+}
+
+remap_enum! {
+    Version: u16 => ssl {
+        TLS_VERSION_1_2 = SSL_LIBRARY_VERSION_TLS_1_2,
+        TLS_VERSION_1_3 = SSL_LIBRARY_VERSION_TLS_1_3,
+    }
+}
+
+mod ciphers {
+    include!(concat!(env!("OUT_DIR"), "/nss_ciphers.rs"));
+}
+
+remap_enum! {
+    Cipher: u16 => ciphers {
+        TLS_AES_128_GCM_SHA256 = TLS_AES_128_GCM_SHA256,
+        TLS_AES_256_GCM_SHA384 = TLS_AES_256_GCM_SHA384,
+        TLS_CHACHA20_POLY1305_SHA256 = TLS_CHACHA20_POLY1305_SHA256,
+    }
+}
+
+remap_enum! {
+    Group: u16 => ssl::SSLNamedGroup {
+        TLS_GRP_EC_SECP256R1 = ssl_grp_ec_secp256r1,
+        TLS_GRP_EC_SECP384R1 = ssl_grp_ec_secp384r1,
+        TLS_GRP_EC_SECP521R1 = ssl_grp_ec_secp521r1,
+        TLS_GRP_EC_X25519 = ssl_grp_ec_curve25519,
+    }
+}
+
+remap_enum! {
+    HandshakeMessage: u8 => ssl::SSLHandshakeType {
+        TLS_HS_HELLO_REQUEST = ssl_hs_hello_request,
+        TLS_HS_CLIENT_HELLO = ssl_hs_client_hello,
+        TLS_HS_SERVER_HELLO = ssl_hs_server_hello,
+        TLS_HS_HELLO_VERIFY_REQUEST = ssl_hs_hello_verify_request,
+        TLS_HS_NEW_SESSION_TICKET = ssl_hs_new_session_ticket,
+        TLS_HS_END_OF_EARLY_DATA = ssl_hs_end_of_early_data,
+        TLS_HS_HELLO_RETRY_REQUEST = ssl_hs_hello_retry_request,
+        TLS_HS_ENCRYPTED_EXTENSIONS = ssl_hs_encrypted_extensions,
+        TLS_HS_CERTIFICATE = ssl_hs_certificate,
+        TLS_HS_SERVER_KEY_EXCHANGE = ssl_hs_server_key_exchange,
+        TLS_HS_CERTIFICATE_REQUEST = ssl_hs_certificate_request,
+        TLS_HS_SERVER_HELLO_DONE = ssl_hs_server_hello_done,
+        TLS_HS_CERTIFICATE_VERIFY = ssl_hs_certificate_verify,
+        TLS_HS_CLIENT_KEY_EXCHANGE = ssl_hs_client_key_exchange,
+        TLS_HS_FINISHED = ssl_hs_finished,
+        TLS_HS_CERT_STATUS = ssl_hs_certificate_status,
+        TLS_HS_KEY_UDPATE = ssl_hs_key_update,
+    }
+}
+
+remap_enum! {
+    Extension: u16 => ssl::SSLExtensionType {
+        TLS_EXT_SERVER_NAME = ssl_server_name_xtn,
+        TLS_EXT_CERT_STATUS = ssl_cert_status_xtn,
+        TLS_EXT_GROUPS = ssl_supported_groups_xtn,
+        TLS_EXT_EC_POINT_FORMATS = ssl_ec_point_formats_xtn,
+        TLS_EXT_SIG_SCHEMES = ssl_signature_algorithms_xtn,
+        TLS_EXT_USE_SRTP = ssl_use_srtp_xtn,
+        TLS_EXT_ALPN = ssl_app_layer_protocol_xtn,
+        TLS_EXT_SCT = ssl_signed_cert_timestamp_xtn,
+        TLS_EXT_PADDING = ssl_padding_xtn,
+        TLS_EXT_EMS = ssl_extended_master_secret_xtn,
+        TLS_EXT_RECORD_SIZE = ssl_record_size_limit_xtn,
+        TLS_EXT_SESSION_TICKET = ssl_session_ticket_xtn,
+        TLS_EXT_PSK = ssl_tls13_pre_shared_key_xtn,
+        TLS_EXT_EARLY_DATA = ssl_tls13_early_data_xtn,
+        TLS_EXT_VERSIONS = ssl_tls13_supported_versions_xtn,
+        TLS_EXT_COOKIE = ssl_tls13_cookie_xtn,
+        TLS_EXT_PSK_MODES = ssl_tls13_psk_key_exchange_modes_xtn,
+        TLS_EXT_CA = ssl_tls13_certificate_authorities_xtn,
+        TLS_EXT_POST_HS_AUTH = ssl_tls13_post_handshake_auth_xtn,
+        TLS_EXT_CERT_SIG_SCHEMES = ssl_signature_algorithms_cert_xtn,
+        TLS_EXT_KEY_SHARE = ssl_tls13_key_share_xtn,
+        TLS_EXT_RENEGOTIATION_INFO = ssl_renegotiation_info_xtn,
+    }
+}
+
+remap_enum! {
+    SignatureScheme: u16 => ssl::SSLSignatureScheme {
+        TLS_SIG_NONE = ssl_sig_none,
+        TLS_SIG_RSA_PKCS1_SHA256 = ssl_sig_rsa_pkcs1_sha256,
+        TLS_SIG_RSA_PKCS1_SHA384 = ssl_sig_rsa_pkcs1_sha384,
+        TLS_SIG_RSA_PKCS1_SHA512 = ssl_sig_rsa_pkcs1_sha512,
+        TLS_SIG_ECDSA_SECP256R1_SHA256 = ssl_sig_ecdsa_secp256r1_sha256,
+        TLS_SIG_ECDSA_SECP384R1_SHA384 = ssl_sig_ecdsa_secp384r1_sha384,
+        TLS_SIG_ECDSA_SECP512R1_SHA512 = ssl_sig_ecdsa_secp521r1_sha512,
+        TLS_SIG_RSA_PSS_RSAE_SHA256 = ssl_sig_rsa_pss_rsae_sha256,
+        TLS_SIG_RSA_PSS_RSAE_SHA384 = ssl_sig_rsa_pss_rsae_sha384,
+        TLS_SIG_RSA_PSS_RSAE_SHA512 = ssl_sig_rsa_pss_rsae_sha512,
+        TLS_SIG_ED25519 = ssl_sig_ed25519,
+        TLS_SIG_ED448 = ssl_sig_ed448,
+        TLS_SIG_RSA_PSS_PSS_SHA256 = ssl_sig_rsa_pss_pss_sha256,
+        TLS_SIG_RSA_PSS_PSS_SHA384 = ssl_sig_rsa_pss_pss_sha384,
+        TLS_SIG_RSA_PSS_PSS_SHA512 = ssl_sig_rsa_pss_pss_sha512,
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/err.rs
@@ -0,0 +1,192 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![allow(dead_code)]
+
+use std::os::raw::c_char;
+
+use crate::ssl::{SECStatus, SECSuccess};
+
+include!(concat!(env!("OUT_DIR"), "/nspr_error.rs"));
+mod codes {
+    #![allow(non_snake_case)]
+    include!(concat!(env!("OUT_DIR"), "/nss_secerr.rs"));
+    include!(concat!(env!("OUT_DIR"), "/nss_sslerr.rs"));
+    include!(concat!(env!("OUT_DIR"), "/mozpkix.rs"));
+}
+pub use codes::mozilla_pkix_ErrorCode as mozpkix;
+pub use codes::SECErrorCodes as sec;
+pub use codes::SSLErrorCodes as ssl;
+pub mod nspr {
+    include!(concat!(env!("OUT_DIR"), "/nspr_err.rs"));
+}
+
+pub type Res<T> = Result<T, Error>;
+
+#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq)]
+#[allow(clippy::pub_enum_variant_names)]
+pub enum Error {
+    AeadInitFailure,
+    AeadError,
+    CertificateLoading,
+    CreateSslSocket,
+    HkdfError,
+    InternalError,
+    IntegerOverflow,
+    InvalidEpoch,
+    MixedHandshakeMethod,
+    NoDataAvailable,
+    NssError {
+        name: String,
+        code: PRErrorCode,
+        desc: String,
+    },
+    OverrunError,
+    SelfEncryptFailure,
+    TimeTravelError,
+    UnsupportedCipher,
+    UnsupportedVersion,
+}
+
+impl std::error::Error for Error {
+    fn cause(&self) -> Option<&dyn std::error::Error> {
+        None
+    }
+
+    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+        None
+    }
+}
+
+impl std::fmt::Display for Error {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        write!(f, "Error: {:?}", self)
+    }
+}
+
+impl From<std::num::TryFromIntError> for Error {
+    fn from(_: std::num::TryFromIntError) -> Self {
+        Error::IntegerOverflow
+    }
+}
+impl From<std::ffi::NulError> for Error {
+    fn from(_: std::ffi::NulError) -> Self {
+        Error::InternalError
+    }
+}
+
+use std::ffi::CStr;
+
+fn wrap_str_fn<F>(f: F, dflt: &str) -> String
+where
+    F: FnOnce() -> *const c_char,
+{
+    unsafe {
+        let p = f();
+        if p.is_null() {
+            return dflt.to_string();
+        }
+        CStr::from_ptr(p).to_string_lossy().into_owned()
+    }
+}
+
+pub fn secstatus_to_res(rv: SECStatus) -> Res<()> {
+    if rv == SECSuccess {
+        return Ok(());
+    }
+
+    let code = unsafe { PR_GetError() };
+    let name = wrap_str_fn(|| unsafe { PR_ErrorToName(code) }, "UNKNOWN_ERROR");
+    let desc = wrap_str_fn(
+        || unsafe { PR_ErrorToString(code, PR_LANGUAGE_I_DEFAULT) },
+        "...",
+    );
+    Err(Error::NssError { name, code, desc })
+}
+
+pub fn is_blocked(result: &Res<()>) -> bool {
+    match result {
+        Err(Error::NssError { code, .. }) => *code == nspr::PR_WOULD_BLOCK_ERROR,
+        _ => false,
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::err::{self, is_blocked, secstatus_to_res, Error, PRErrorCode, PR_SetError};
+    use crate::ssl::{SECFailure, SECSuccess};
+    use test_fixture::fixture_init;
+
+    fn set_error_code(code: PRErrorCode) {
+        // This code doesn't work without initializing NSS first.
+        fixture_init();
+        unsafe { PR_SetError(code, 0) };
+    }
+
+    #[test]
+    fn error_code() {
+        fixture_init();
+        assert_eq!(15 - 0x3000, err::ssl::SSL_ERROR_BAD_MAC_READ);
+        assert_eq!(166 - 0x2000, err::sec::SEC_ERROR_LIBPKIX_INTERNAL);
+        assert_eq!(-5998, err::nspr::PR_WOULD_BLOCK_ERROR);
+    }
+
+    #[test]
+    fn is_ok() {
+        assert!(secstatus_to_res(SECSuccess).is_ok());
+    }
+
+    #[test]
+    fn is_err() {
+        set_error_code(err::ssl::SSL_ERROR_BAD_MAC_READ);
+        let r = secstatus_to_res(SECFailure);
+        assert!(r.is_err());
+        match r.unwrap_err() {
+            Error::NssError { name, code, desc } => {
+                assert_eq!(name, "SSL_ERROR_BAD_MAC_READ");
+                assert_eq!(code, -12273);
+                assert_eq!(
+                    desc,
+                    "SSL received a record with an incorrect Message Authentication Code."
+                );
+            }
+            _ => unreachable!(),
+        }
+    }
+
+    #[test]
+    fn is_err_zero_code() {
+        set_error_code(0);
+        let r = secstatus_to_res(SECFailure);
+        assert!(r.is_err());
+        match r.unwrap_err() {
+            Error::NssError { name, code, .. } => {
+                assert_eq!(name, "UNKNOWN_ERROR");
+                assert_eq!(code, 0);
+                // Note that we don't test |desc| here because that comes from
+                // strerror(0), which is platform-dependent.
+            }
+            _ => unreachable!(),
+        }
+    }
+
+    #[test]
+    fn blocked() {
+        set_error_code(err::nspr::PR_WOULD_BLOCK_ERROR);
+        let r = secstatus_to_res(SECFailure);
+        assert!(r.is_err());
+        assert!(is_blocked(&r));
+        match r.unwrap_err() {
+            Error::NssError { name, code, desc } => {
+                assert_eq!(name, "PR_WOULD_BLOCK_ERROR");
+                assert_eq!(code, -5998);
+                assert_eq!(desc, "The operation would have blocked");
+            }
+            _ => panic!("bad error type"),
+        }
+    }
+
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/exp.rs
@@ -0,0 +1,23 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+macro_rules! experimental_api {
+    ( $n:ident ( $( $a:ident : $t:ty ),* $(,)? ) ) => {
+        #[allow(non_snake_case)]
+        #[allow(clippy::too_many_arguments)]
+        pub(crate) unsafe fn $n ( $( $a : $t ),* ) -> Result<(), crate::err::Error> {
+            const EXP_FUNCTION: &str = stringify!($n);
+            let n = ::std::ffi::CString::new(EXP_FUNCTION)?;
+            let f = crate::ssl::SSL_GetExperimentalAPI(n.as_ptr());
+            if f.is_null() {
+                return Err(crate::err::Error::InternalError);
+            }
+            let f: unsafe extern "C" fn( $( $t ),* ) -> crate::ssl::SECStatus = ::std::mem::transmute(f);
+            let rv = f( $( $a ),* );
+            crate::err::secstatus_to_res(rv)
+        }
+    };
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/ext.rs
@@ -0,0 +1,149 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::constants::*;
+use crate::err::Res;
+use crate::ssl::{
+    PRBool, PRFileDesc, SECFailure, SECStatus, SECSuccess, SSLAlertDescription,
+    SSLExtensionHandler, SSLExtensionWriter, SSLHandshakeType,
+};
+
+use std::cell::RefCell;
+use std::convert::TryFrom;
+use std::ops::DerefMut;
+use std::os::raw::{c_uint, c_void};
+use std::rc::Rc;
+
+experimental_api!(SSL_InstallExtensionHooks(
+    fd: *mut PRFileDesc,
+    extension: u16,
+    writer: SSLExtensionWriter,
+    writer_arg: *mut c_void,
+    handler: SSLExtensionHandler,
+    handler_arg: *mut c_void,
+));
+
+pub enum ExtensionWriterResult {
+    Write(usize),
+    Skip,
+}
+
+pub enum ExtensionHandlerResult {
+    Ok,
+    Alert(crate::constants::Alert),
+}
+
+pub trait ExtensionHandler {
+    fn write(&mut self, msg: HandshakeMessage, _d: &mut [u8]) -> ExtensionWriterResult {
+        match msg {
+            TLS_HS_CLIENT_HELLO | TLS_HS_ENCRYPTED_EXTENSIONS => ExtensionWriterResult::Write(0),
+            _ => ExtensionWriterResult::Skip,
+        }
+    }
+
+    fn handle(&mut self, msg: HandshakeMessage, _d: &[u8]) -> ExtensionHandlerResult {
+        match msg {
+            TLS_HS_CLIENT_HELLO | TLS_HS_ENCRYPTED_EXTENSIONS => ExtensionHandlerResult::Ok,
+            _ => ExtensionHandlerResult::Alert(110), // unsupported_extension
+        }
+    }
+}
+
+pub struct ExtensionTracker {
+    extension: Extension,
+    handler: Box<Box<Rc<RefCell<dyn ExtensionHandler>>>>,
+}
+
+impl ExtensionTracker {
+    // Technically the as_mut() call here is the only unsafe bit,
+    // but don't call this function lightly.
+    unsafe fn wrap_handler_call<F, T>(arg: *mut c_void, f: F) -> T
+    where
+        F: FnOnce(&mut dyn ExtensionHandler) -> T,
+    {
+        let handler_ptr = arg as *mut Box<Rc<RefCell<dyn ExtensionHandler>>>;
+        let rc = handler_ptr.as_mut().unwrap();
+        f(rc.borrow_mut().deref_mut())
+    }
+
+    #[allow(clippy::cast_possible_truncation)]
+    unsafe extern "C" fn extension_writer(
+        _fd: *mut PRFileDesc,
+        message: SSLHandshakeType::Type,
+        data: *mut u8,
+        len: *mut c_uint,
+        max_len: c_uint,
+        arg: *mut c_void,
+    ) -> PRBool {
+        let d = std::slice::from_raw_parts_mut(data, max_len as usize);
+        Self::wrap_handler_call(arg, |handler| {
+            // Cast is safe here because the message type is always part of the enum
+            match handler.write(message as HandshakeMessage, d) {
+                ExtensionWriterResult::Write(sz) => {
+                    *len = c_uint::try_from(sz).expect("integer overflow from extension writer");
+                    1
+                }
+                ExtensionWriterResult::Skip => 0,
+            }
+        })
+    }
+
+    unsafe extern "C" fn extension_handler(
+        _fd: *mut PRFileDesc,
+        message: SSLHandshakeType::Type,
+        data: *const u8,
+        len: c_uint,
+        alert: *mut SSLAlertDescription,
+        arg: *mut c_void,
+    ) -> SECStatus {
+        let d = std::slice::from_raw_parts(data, len as usize);
+        #[allow(clippy::cast_possible_truncation)]
+        Self::wrap_handler_call(arg, |handler| {
+            // Cast is safe here because the message type is always part of the enum
+            match handler.handle(message as HandshakeMessage, d) {
+                ExtensionHandlerResult::Ok => SECSuccess,
+                ExtensionHandlerResult::Alert(a) => {
+                    *alert = a;
+                    SECFailure
+                }
+            }
+        })
+    }
+
+    pub unsafe fn new(
+        fd: *mut PRFileDesc,
+        extension: Extension,
+        handler: Rc<RefCell<dyn ExtensionHandler>>,
+    ) -> Res<Self> {
+        // The ergonomics here aren't great for users of this API, but it's
+        // even worse here. The double box is used to allow us to own a reference
+        // to the handler AND also pass a bare pointer to the inner box to C.
+        // That allows us to create the object in the callback. The inner box prevents
+        // the access in the callback from decrementing the Rc counters when the
+        // duped instance is dropped.  That would result in the object being dropped
+        // in the callbacks (and the UAF that follows).
+        let mut tracker = Self {
+            extension,
+            handler: Box::new(Box::new(handler)),
+        };
+        let p = &mut *tracker.handler as *mut Box<Rc<RefCell<dyn ExtensionHandler>>> as *mut c_void;
+        SSL_InstallExtensionHooks(
+            fd,
+            extension,
+            Some(Self::extension_writer),
+            p,
+            Some(Self::extension_handler),
+            p,
+        )?;
+        Ok(tracker)
+    }
+}
+
+impl std::fmt::Debug for ExtensionTracker {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        write!(f, "ExtensionTracker: {:?}", self.extension)
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/hkdf.rs
@@ -0,0 +1,136 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::constants::*;
+use crate::err::{Error, Res};
+use crate::p11::{
+    random, PK11Origin, PK11SymKey, PK11_GetInternalSlot, PK11_ImportSymKey, SECItem, SECItemType,
+    Slot, SymKey, CKA_DERIVE, CKM_NSS_HKDF_SHA256, CKM_NSS_HKDF_SHA384, CK_ATTRIBUTE_TYPE,
+    CK_MECHANISM_TYPE,
+};
+
+use std::convert::TryFrom;
+use std::os::raw::{c_char, c_uchar, c_uint};
+use std::ptr::{null_mut, NonNull};
+
+experimental_api!(SSL_HkdfExtract(
+    version: Version,
+    cipher: Cipher,
+    salt: *mut PK11SymKey,
+    ikm: *mut PK11SymKey,
+    prk: *mut *mut PK11SymKey,
+));
+experimental_api!(SSL_HkdfExpandLabel(
+    version: Version,
+    cipher: Cipher,
+    prk: *mut PK11SymKey,
+    handshake_hash: *const u8,
+    handshake_hash_len: c_uint,
+    label: *const c_char,
+    label_len: c_uint,
+    secret: *mut *mut PK11SymKey,
+));
+
+pub fn key_size(version: Version, cipher: Cipher) -> Res<usize> {
+    if version != TLS_VERSION_1_3 {
+        return Err(Error::UnsupportedVersion);
+    }
+    Ok(match cipher {
+        TLS_AES_128_GCM_SHA256 | TLS_CHACHA20_POLY1305_SHA256 => 32,
+        TLS_AES_256_GCM_SHA384 => 48,
+        _ => return Err(Error::UnsupportedCipher),
+    })
+}
+
+pub fn generate_key(version: Version, cipher: Cipher, size: usize) -> Res<SymKey> {
+    import_key(version, cipher, &random(size)?)
+}
+
+/// Import a symmetric key for use with HKDF.
+pub fn import_key(version: Version, cipher: Cipher, buf: &[u8]) -> Res<SymKey> {
+    if version != TLS_VERSION_1_3 {
+        return Err(Error::UnsupportedVersion);
+    }
+    let mech = match cipher {
+        TLS_AES_128_GCM_SHA256 | TLS_CHACHA20_POLY1305_SHA256 => CKM_NSS_HKDF_SHA256,
+        TLS_AES_256_GCM_SHA384 => CKM_NSS_HKDF_SHA384,
+        _ => return Err(Error::UnsupportedCipher),
+    };
+    let mut item = SECItem {
+        type_: SECItemType::siBuffer,
+        data: buf.as_ptr() as *mut c_uchar,
+        len: c_uint::try_from(buf.len())?,
+    };
+    let slot_ptr = unsafe { PK11_GetInternalSlot() };
+    let slot = match NonNull::new(slot_ptr) {
+        Some(p) => Slot::new(p),
+        None => return Err(Error::InternalError),
+    };
+    let key_ptr = unsafe {
+        PK11_ImportSymKey(
+            *slot,
+            CK_MECHANISM_TYPE::from(mech),
+            PK11Origin::PK11_OriginUnwrap,
+            CK_ATTRIBUTE_TYPE::from(CKA_DERIVE),
+            &mut item,
+            null_mut(),
+        )
+    };
+    match NonNull::new(key_ptr) {
+        Some(p) => Ok(SymKey::new(p)),
+        None => Err(Error::InternalError),
+    }
+}
+
+/// Extract a PRK from the given salt and IKM using the algorithm defined in RFC 5869.
+pub fn extract(
+    version: Version,
+    cipher: Cipher,
+    salt: Option<&SymKey>,
+    ikm: &SymKey,
+) -> Res<SymKey> {
+    let mut prk: *mut PK11SymKey = null_mut();
+    let salt_ptr: *mut PK11SymKey = match salt {
+        Some(s) => **s,
+        None => null_mut(),
+    };
+    unsafe { SSL_HkdfExtract(version, cipher, salt_ptr, **ikm, &mut prk) }?;
+    match NonNull::new(prk) {
+        Some(p) => Ok(SymKey::new(p)),
+        None => Err(Error::InternalError),
+    }
+}
+
+/// Expand a PRK using the HKDF-Expand-Label function defined in RFC 8446.
+pub fn expand_label(
+    version: Version,
+    cipher: Cipher,
+    prk: &SymKey,
+    handshake_hash: &[u8],
+    label: &str,
+) -> Res<SymKey> {
+    let l = label.as_bytes();
+    let mut secret: *mut PK11SymKey = null_mut();
+
+    // Note that this doesn't allow for passing null() for the handshake hash.
+    // A zero-length slice produces an identical result.
+    unsafe {
+        SSL_HkdfExpandLabel(
+            version,
+            cipher,
+            **prk,
+            handshake_hash.as_ptr(),
+            c_uint::try_from(handshake_hash.len())?,
+            l.as_ptr() as *const c_char,
+            c_uint::try_from(l.len())?,
+            &mut secret,
+        )
+    }?;
+    match NonNull::new(secret) {
+        Some(p) => Ok(SymKey::new(p)),
+        None => Err(Error::HkdfError),
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/hp.rs
@@ -0,0 +1,115 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::constants::*;
+use crate::err::{secstatus_to_res, Error, Res};
+use crate::p11::{
+    PK11SymKey, PK11_Encrypt, PK11_GetBlockSize, PK11_GetMechanism, SECItem, SECItemType, SymKey,
+    CKM_AES_ECB, CKM_NSS_CHACHA20_CTR, CK_MECHANISM_TYPE,
+};
+
+use std::convert::TryFrom;
+use std::fmt::{self, Debug};
+use std::os::raw::{c_char, c_uint};
+use std::ptr::{null, null_mut, NonNull};
+
+experimental_api!(SSL_HkdfExpandLabelWithMech(
+    version: Version,
+    cipher: Cipher,
+    prk: *mut PK11SymKey,
+    handshake_hash: *const u8,
+    handshake_hash_len: c_uint,
+    label: *const c_char,
+    label_len: c_uint,
+    mech: CK_MECHANISM_TYPE,
+    key_size: c_uint,
+    secret: *mut *mut PK11SymKey,
+));
+
+pub struct HpKey(SymKey);
+
+impl Debug for HpKey {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.write_str("HP Key")
+    }
+}
+
+impl HpKey {
+    /// QUIC-specific API for extracting a header-protection key.
+    pub fn extract(version: Version, cipher: Cipher, prk: &SymKey, label: &str) -> Res<Self> {
+        let l = label.as_bytes();
+        let mut secret: *mut PK11SymKey = null_mut();
+
+        let (mech, key_size) = match cipher {
+            TLS_AES_128_GCM_SHA256 => (CK_MECHANISM_TYPE::from(CKM_AES_ECB), 16),
+            TLS_AES_256_GCM_SHA384 => (CK_MECHANISM_TYPE::from(CKM_AES_ECB), 32),
+            TLS_CHACHA20_POLY1305_SHA256 => (CK_MECHANISM_TYPE::from(CKM_NSS_CHACHA20_CTR), 32),
+            _ => unreachable!(),
+        };
+
+        // Note that this doesn't allow for passing null() for the handshake hash.
+        // A zero-length slice produces an identical result.
+        unsafe {
+            SSL_HkdfExpandLabelWithMech(
+                version,
+                cipher,
+                **prk,
+                null(),
+                0,
+                l.as_ptr() as *const c_char,
+                c_uint::try_from(l.len())?,
+                mech,
+                key_size,
+                &mut secret,
+            )
+        }?;
+        match NonNull::new(secret) {
+            None => Err(Error::HkdfError),
+            Some(p) => Ok(Self(SymKey::new(p))),
+        }
+    }
+
+    /// Generate a header protection mask for QUIC.
+    #[allow(clippy::cast_sign_loss)]
+    pub fn mask(&self, sample: &[u8]) -> Res<Vec<u8>> {
+        let k: *mut PK11SymKey = *self.0;
+        let mech = unsafe { PK11_GetMechanism(k) };
+        // Cast is safe because block size is always greater than or equal to 0
+        let block_size = unsafe { PK11_GetBlockSize(mech, null_mut()) } as usize;
+
+        let mut output = vec![0_u8; block_size];
+        let output_slice = &mut output[..];
+        let mut output_len: c_uint = 0;
+
+        let mut item = SECItem {
+            type_: SECItemType::siBuffer,
+            data: sample.as_ptr() as *mut u8,
+            len: c_uint::try_from(sample.len())?,
+        };
+        let zero = vec![0_u8; block_size];
+        let (iv, inbuf) = match () {
+            _ if mech == CK_MECHANISM_TYPE::from(CKM_AES_ECB) => (null_mut(), sample),
+            _ if mech == CK_MECHANISM_TYPE::from(CKM_NSS_CHACHA20_CTR) => {
+                (&mut item as *mut SECItem, &zero[..])
+            }
+            _ => unreachable!(),
+        };
+        secstatus_to_res(unsafe {
+            PK11_Encrypt(
+                k,
+                mech,
+                iv,
+                output_slice.as_mut_ptr(),
+                &mut output_len,
+                c_uint::try_from(output.len())?,
+                inbuf.as_ptr() as *const u8,
+                c_uint::try_from(inbuf.len())?,
+            )
+        })?;
+        assert_eq!(output_len as usize, block_size);
+        Ok(output)
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/lib.rs
@@ -0,0 +1,148 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![deny(warnings)]
+// Bindgen auto generated code
+// won't adhere to the clippy rules below
+#![allow(clippy::module_name_repetitions)]
+#![allow(clippy::unseparated_literal_suffix)]
+
+#[macro_use]
+mod exp;
+#[macro_use]
+mod p11;
+
+pub mod aead;
+pub mod agent;
+mod agentio;
+mod auth;
+mod cert;
+pub mod constants;
+mod err;
+pub mod ext;
+pub mod hkdf;
+pub mod hp;
+mod prio;
+mod replay;
+mod secrets;
+pub mod selfencrypt;
+mod ssl;
+mod time;
+
+pub use self::agent::{
+    Agent, Client, HandshakeState, Record, RecordList, SecretAgent, SecretAgentInfo,
+    SecretAgentPreInfo, Server, ZeroRttCheckResult, ZeroRttChecker,
+};
+pub use self::constants::*;
+pub use self::err::{Error, PRErrorCode, Res};
+pub use self::ext::{ExtensionHandler, ExtensionHandlerResult, ExtensionWriterResult};
+pub use self::p11::SymKey;
+pub use self::replay::AntiReplay;
+pub use self::secrets::SecretDirection;
+pub use auth::AuthenticationStatus;
+
+use neqo_common::once::OnceResult;
+
+use std::ffi::CString;
+use std::os::raw::c_char;
+use std::path::{Path, PathBuf};
+use std::ptr::null;
+
+mod nss {
+    #![allow(clippy::redundant_static_lifetimes, non_upper_case_globals)]
+    include!(concat!(env!("OUT_DIR"), "/nss_init.rs"));
+}
+
+// Need to map the types through.
+fn secstatus_to_res(code: nss::SECStatus) -> Res<()> {
+    crate::err::secstatus_to_res(code as crate::ssl::SECStatus)
+}
+
+enum NssLoaded {
+    External,
+    NoDb,
+    Db(Box<Path>),
+}
+
+impl Drop for NssLoaded {
+    fn drop(&mut self) {
+        match self {
+            NssLoaded::NoDb | NssLoaded::Db(_) => unsafe {
+                secstatus_to_res(nss::NSS_Shutdown()).expect("NSS Shutdown failed")
+            },
+            _ => {}
+        }
+    }
+}
+
+static mut INITIALIZED: OnceResult<NssLoaded> = OnceResult::new();
+
+fn already_initialized() -> bool {
+    unsafe { nss::NSS_IsInitialized() != 0 }
+}
+
+/// Initialize NSS.  This only executes the initialization routines once, so if there is any chance that
+pub fn init() {
+    // Set time zero.
+    time::init();
+    unsafe {
+        INITIALIZED.call_once(|| {
+            if already_initialized() {
+                return NssLoaded::External;
+            }
+
+            secstatus_to_res(nss::NSS_NoDB_Init(null())).expect("NSS_NoDB_Init failed");
+            secstatus_to_res(nss::NSS_SetDomesticPolicy()).expect("NSS_SetDomesticPolicy failed");
+
+            NssLoaded::NoDb
+        });
+    }
+}
+
+pub fn init_db<P: Into<PathBuf>>(dir: P) {
+    time::init();
+    unsafe {
+        INITIALIZED.call_once(|| {
+            if already_initialized() {
+                return NssLoaded::External;
+            }
+
+            let path = dir.into();
+            assert!(path.is_dir());
+            let pathstr = path.to_str().expect("path converts to string").to_string();
+            let dircstr = CString::new(pathstr).expect("new CString");
+            let empty = CString::new("").expect("new empty CString");
+            secstatus_to_res(nss::NSS_Initialize(
+                dircstr.as_ptr(),
+                empty.as_ptr(),
+                empty.as_ptr(),
+                nss::SECMOD_DB.as_ptr() as *const c_char,
+                nss::NSS_INIT_READONLY,
+            ))
+            .expect("NSS_Initialize failed");
+
+            secstatus_to_res(nss::NSS_SetDomesticPolicy()).expect("NSS_SetDomesticPolicy failed");
+            secstatus_to_res(ssl::SSL_ConfigServerSessionIDCache(
+                1024,
+                0,
+                0,
+                dircstr.as_ptr(),
+            ))
+            .expect("SSL_ConfigServerSessionIDCache failed");
+
+            NssLoaded::Db(path.to_path_buf().into_boxed_path())
+        });
+    }
+}
+
+/// Panic if NSS isn't initialized.
+pub fn assert_initialized() {
+    unsafe {
+        INITIALIZED.call_once(|| {
+            panic!("NSS not initialized with init or init_db");
+        });
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/p11.rs
@@ -0,0 +1,108 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![allow(dead_code)]
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+
+use crate::err::{secstatus_to_res, Error, Res};
+
+use neqo_common::hex;
+
+use std::convert::TryInto;
+use std::ops::{Deref, DerefMut};
+use std::ptr::NonNull;
+
+#[allow(clippy::unreadable_literal)]
+mod nss_p11 {
+    include!(concat!(env!("OUT_DIR"), "/nss_p11.rs"));
+}
+
+pub use nss_p11::*;
+
+macro_rules! scoped_ptr {
+    ($scoped:ident, $target:ty, $dtor:path) => {
+        pub struct $scoped {
+            ptr: *mut $target,
+        }
+
+        impl $scoped {
+            pub fn new(ptr: NonNull<$target>) -> Self {
+                Self { ptr: ptr.as_ptr() }
+            }
+        }
+
+        impl Deref for $scoped {
+            type Target = *mut $target;
+            fn deref(&self) -> &*mut $target {
+                &self.ptr
+            }
+        }
+
+        impl DerefMut for $scoped {
+            fn deref_mut(&mut self) -> &mut *mut $target {
+                &mut self.ptr
+            }
+        }
+
+        impl Drop for $scoped {
+            fn drop(&mut self) {
+                let _ = unsafe { $dtor(self.ptr) };
+            }
+        }
+    };
+}
+
+scoped_ptr!(Certificate, CERTCertificate, CERT_DestroyCertificate);
+scoped_ptr!(CertList, CERTCertList, CERT_DestroyCertList);
+scoped_ptr!(PrivateKey, SECKEYPrivateKey, SECKEY_DestroyPrivateKey);
+scoped_ptr!(SymKey, PK11SymKey, PK11_FreeSymKey);
+scoped_ptr!(Slot, PK11SlotInfo, PK11_FreeSlot);
+
+impl SymKey {
+    /// You really don't want to use this.
+    pub fn as_bytes<'a>(&'a self) -> Res<&'a [u8]> {
+        secstatus_to_res(unsafe { PK11_ExtractKeyValue(self.ptr) })?;
+
+        let key_item = unsafe { PK11_GetKeyData(self.ptr) };
+        // This is accessing a value attached to the key, so we can treat this as a borrow.
+        match unsafe { key_item.as_mut() } {
+            None => Err(Error::InternalError),
+            Some(key) => Ok(unsafe { std::slice::from_raw_parts(key.data, key.len as usize) }),
+        }
+    }
+}
+
+impl std::fmt::Debug for SymKey {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        if let Ok(b) = self.as_bytes() {
+            write!(f, "SymKey {}", hex(b))
+        } else {
+            write!(f, "Opaque SymKey")
+        }
+    }
+}
+
+/// Generate a randomized buffer.
+pub fn random(size: usize) -> Res<Vec<u8>> {
+    let mut buf = vec![0; size];
+    secstatus_to_res(unsafe { PK11_GenerateRandom(buf.as_mut_ptr(), buf.len().try_into()?) })?;
+    Ok(buf)
+}
+
+#[cfg(test)]
+mod test {
+    use super::random;
+    use test_fixture::fixture_init;
+
+    #[test]
+    fn randomness() {
+        fixture_init();
+        // If this ever fails, there is either a bug, or it's time to buy a lottery ticket.
+        assert_ne!(random(16), random(16));
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/prio.rs
@@ -0,0 +1,19 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![allow(dead_code)]
+#![allow(non_upper_case_globals)]
+#![allow(non_snake_case)]
+#![allow(clippy::cognitive_complexity)]
+#![allow(clippy::empty_enum)]
+
+include!(concat!(env!("OUT_DIR"), "/nspr_io.rs"));
+
+pub enum PRFileInfo {}
+pub enum PRFileInfo64 {}
+pub enum PRFilePrivate {}
+pub enum PRIOVec {}
+pub enum PRSendFileData {}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/replay.rs
@@ -0,0 +1,76 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::err::{Error, Res};
+use crate::ssl::PRFileDesc;
+use crate::time::{Interval, PRTime, Time};
+
+use std::convert::{TryFrom, TryInto};
+use std::ops::{Deref, DerefMut};
+use std::os::raw::c_uint;
+use std::ptr::{null_mut, NonNull};
+use std::time::{Duration, Instant};
+
+// This is an opaque struct in NSS.
+#[allow(clippy::empty_enum)]
+pub enum SSLAntiReplayContext {}
+
+experimental_api!(SSL_CreateAntiReplayContext(
+    now: PRTime,
+    window: PRTime,
+    k: c_uint,
+    bits: c_uint,
+    ctx: *mut *mut SSLAntiReplayContext,
+));
+experimental_api!(SSL_ReleaseAntiReplayContext(ctx: *mut SSLAntiReplayContext,));
+experimental_api!(SSL_SetAntiReplayContext(
+    fd: *mut PRFileDesc,
+    ctx: *mut SSLAntiReplayContext,
+));
+
+scoped_ptr!(
+    AntiReplayContext,
+    SSLAntiReplayContext,
+    SSL_ReleaseAntiReplayContext
+);
+
+/// `AntiReplay` is used by servers when processing 0-RTT handshakes.
+/// It limits the exposure of servers to replay attack by rejecting 0-RTT
+/// if it appears to be a replay.  There is a false-positive rate that can be
+/// managed by tuning the parameters used to create the context.
+#[allow(clippy::module_name_repetitions)]
+pub struct AntiReplay {
+    ctx: AntiReplayContext,
+}
+
+impl AntiReplay {
+    /// Make a new anti-replay context.
+    /// See the documentation in NSS for advice on how to set these values.
+    pub fn new(now: Instant, window: Duration, k: usize, bits: usize) -> Res<Self> {
+        let mut ctx: *mut SSLAntiReplayContext = null_mut();
+        unsafe {
+            SSL_CreateAntiReplayContext(
+                Time::from(now).try_into()?,
+                Interval::from(window).try_into()?,
+                c_uint::try_from(k)?,
+                c_uint::try_from(bits)?,
+                &mut ctx,
+            )
+        }?;
+
+        match NonNull::new(ctx) {
+            Some(ctx_nn) => Ok(Self {
+                ctx: AntiReplayContext::new(ctx_nn),
+            }),
+            None => Err(Error::InternalError),
+        }
+    }
+
+    /// Configure the provided socket with this anti-replay context.
+    pub(crate) fn config_socket(&self, fd: *mut PRFileDesc) -> Res<()> {
+        unsafe { SSL_SetAntiReplayContext(fd, *self.ctx) }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/result.rs
@@ -0,0 +1,133 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::err::{
+    nspr, Error, PR_ErrorToName, PR_ErrorToString, PR_GetError, Res, PR_LANGUAGE_I_DEFAULT,
+};
+use crate::ssl;
+
+use std::ffi::CStr;
+
+pub fn result(rv: ssl::SECStatus) -> Res<()> {
+    let _ = result_helper(rv, false)?;
+    Ok(())
+}
+
+pub fn result_or_blocked(rv: ssl::SECStatus) -> Res<bool> {
+    result_helper(rv, true)
+}
+
+fn wrap_str_fn<F>(f: F, dflt: &str) -> String
+where
+    F: FnOnce() -> *const i8,
+{
+    unsafe {
+        let p = f();
+        if p.is_null() {
+            return dflt.to_string();
+        }
+        CStr::from_ptr(p).to_string_lossy().into_owned()
+    }
+}
+
+fn result_helper(rv: ssl::SECStatus, allow_blocked: bool) -> Res<bool> {
+    if rv == ssl::_SECStatus_SECSuccess {
+        return Ok(false);
+    }
+
+    let code = unsafe { PR_GetError() };
+    if allow_blocked && code == nspr::PR_WOULD_BLOCK_ERROR {
+        return Ok(true);
+    }
+
+    let name = wrap_str_fn(|| unsafe { PR_ErrorToName(code) }, "UNKNOWN_ERROR");
+    let desc = wrap_str_fn(
+        || unsafe { PR_ErrorToString(code, PR_LANGUAGE_I_DEFAULT) },
+        "...",
+    );
+    Err(Error::NssError { name, code, desc })
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{result, result_or_blocked};
+    use crate::err::{self, nspr, Error, PRErrorCode, PR_SetError};
+    use crate::ssl;
+    use test_fixture::fixture_init;
+
+    fn set_error_code(code: PRErrorCode) {
+        unsafe { PR_SetError(code, 0) };
+    }
+
+    #[test]
+    fn is_ok() {
+        assert!(result(ssl::SECSuccess).is_ok());
+    }
+
+    #[test]
+    fn is_err() {
+        // This code doesn't work without initializing NSS first.
+        fixture_init();
+
+        set_error_code(err::ssl::SSL_ERROR_BAD_MAC_READ);
+        let r = result(ssl::SECFailure);
+        assert!(r.is_err());
+        match r.unwrap_err() {
+            Error::NssError { name, code, desc } => {
+                assert_eq!(name, "SSL_ERROR_BAD_MAC_READ");
+                assert_eq!(code, -12273);
+                assert_eq!(
+                    desc,
+                    "SSL received a record with an incorrect Message Authentication Code."
+                );
+            }
+            _ => unreachable!(),
+        }
+    }
+
+    #[test]
+    fn is_err_zero_code() {
+        // This code doesn't work without initializing NSS first.
+        fixture_init();
+
+        set_error_code(0);
+        let r = result(ssl::SECFailure);
+        assert!(r.is_err());
+        match r.unwrap_err() {
+            Error::NssError { name, code, .. } => {
+                assert_eq!(name, "UNKNOWN_ERROR");
+                assert_eq!(code, 0);
+                // Note that we don't test |desc| here because that comes from
+                // strerror(0), which is platform-dependent.
+            }
+            _ => unreachable!(),
+        }
+    }
+
+    #[test]
+    fn blocked_as_error() {
+        // This code doesn't work without initializing NSS first.
+        fixture_init();
+
+        set_error_code(nspr::PR_WOULD_BLOCK_ERROR);
+        let r = result(ssl::SECFailure);
+        assert!(r.is_err());
+        match r.unwrap_err() {
+            Error::NssError { name, code, desc } => {
+                assert_eq!(name, "PR_WOULD_BLOCK_ERROR");
+                assert_eq!(code, -5998);
+                assert_eq!(desc, "The operation would have blocked");
+            }
+            _ => panic!("bad error type"),
+        }
+    }
+
+    #[test]
+    fn is_blocked() {
+        set_error_code(nspr::PR_WOULD_BLOCK_ERROR);
+        assert!(result_or_blocked(ssl::SECFailure).unwrap());
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/secrets.rs
@@ -0,0 +1,128 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::constants::*;
+use crate::err::Res;
+use crate::p11::{PK11SymKey, PK11_ReferenceSymKey, SymKey};
+use crate::ssl::{PRFileDesc, SSLSecretCallback, SSLSecretDirection};
+
+use neqo_common::qdebug;
+use std::ops::Deref;
+use std::os::raw::c_void;
+use std::ptr::NonNull;
+
+experimental_api!(SSL_SecretCallback(
+    fd: *mut PRFileDesc,
+    cb: SSLSecretCallback,
+    arg: *mut c_void,
+));
+
+#[derive(Clone, Copy, Debug)]
+pub enum SecretDirection {
+    Read,
+    Write,
+}
+
+impl From<SSLSecretDirection::Type> for SecretDirection {
+    fn from(dir: SSLSecretDirection::Type) -> Self {
+        match dir {
+            SSLSecretDirection::ssl_secret_read => SecretDirection::Read,
+            SSLSecretDirection::ssl_secret_write => SecretDirection::Write,
+            _ => unreachable!(),
+        }
+    }
+}
+
+#[derive(Debug, Default)]
+#[allow(clippy::module_name_repetitions)]
+pub struct DirectionalSecrets {
+    // We only need to maintain 3 secrets for the epochs used during the handshake.
+    secrets: [Option<SymKey>; 3],
+}
+
+impl DirectionalSecrets {
+    pub fn put(&mut self, epoch: Epoch, key: SymKey) {
+        assert!(epoch > 0);
+        let i = (epoch - 1) as usize;
+        assert!(i < self.secrets.len());
+        // assert!(self.secrets[i].is_none());
+        self.secrets[i] = Some(key);
+    }
+
+    pub fn get(&self, epoch: Epoch) -> Option<&SymKey> {
+        assert!(epoch > 0);
+        let i = (epoch - 1) as usize;
+        assert!(i < self.secrets.len());
+        self.secrets[i].as_ref()
+    }
+}
+
+#[derive(Debug, Default)]
+pub struct Secrets {
+    r: DirectionalSecrets,
+    w: DirectionalSecrets,
+}
+
+impl Secrets {
+    unsafe extern "C" fn secret_available(
+        _fd: *mut PRFileDesc,
+        epoch: u16,
+        dir: SSLSecretDirection::Type,
+        secret: *mut PK11SymKey,
+        arg: *mut c_void,
+    ) {
+        let secrets_ptr = arg as *mut Self;
+        let secrets = secrets_ptr.as_mut().unwrap();
+        secrets.put_raw(epoch, dir, secret);
+    }
+
+    fn put_raw(&mut self, epoch: Epoch, dir: SSLSecretDirection::Type, key_ptr: *mut PK11SymKey) {
+        let key_ptr = unsafe { PK11_ReferenceSymKey(key_ptr) };
+        let key = match NonNull::new(key_ptr) {
+            None => panic!("NSS shouldn't be passing out NULL secrets"),
+            Some(p) => SymKey::new(p),
+        };
+        self.put(dir.into(), epoch, key);
+    }
+
+    pub fn put(&mut self, dir: SecretDirection, epoch: Epoch, key: SymKey) {
+        qdebug!("{:?} secret available for {:?}", dir, epoch);
+        let keys = match dir {
+            SecretDirection::Read => &mut self.r,
+            SecretDirection::Write => &mut self.w,
+        };
+        keys.put(epoch, key);
+    }
+
+    pub fn read(&self) -> &DirectionalSecrets {
+        &self.r
+    }
+
+    pub fn write(&self) -> &DirectionalSecrets {
+        &self.w
+    }
+}
+
+#[derive(Debug, Default)]
+pub struct SecretHolder {
+    secrets: Box<Secrets>,
+}
+
+impl SecretHolder {
+    /// This registers with NSS.  The lifetime of this object needs to match the lifetime
+    /// of the connection, or bad things might happen.
+    pub fn register(&mut self, fd: *mut PRFileDesc) -> Res<()> {
+        let p = &*self.secrets as *const Secrets as *const c_void;
+        unsafe { SSL_SecretCallback(fd, Some(Secrets::secret_available), p as *mut c_void) }
+    }
+}
+
+impl Deref for SecretHolder {
+    type Target = Secrets;
+    fn deref(&self) -> &Self::Target {
+        self.secrets.as_ref()
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/selfencrypt.rs
@@ -0,0 +1,144 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::aead::Aead;
+use crate::constants::*;
+use crate::err::{Error, Res};
+use crate::hkdf;
+use crate::p11::{random, SymKey};
+
+use neqo_common::{hex, qinfo, qtrace, Encoder};
+
+use std::mem;
+
+#[derive(Debug)]
+pub struct SelfEncrypt {
+    version: Version,
+    cipher: Cipher,
+    key_id: u8,
+    key: SymKey,
+    old_key: Option<SymKey>,
+}
+
+impl SelfEncrypt {
+    const VERSION: u8 = 1;
+    const SALT_LENGTH: usize = 16;
+
+    pub fn new(version: Version, cipher: Cipher) -> Res<Self> {
+        let sz = hkdf::key_size(version, cipher)?;
+        let key = hkdf::generate_key(version, cipher, sz)?;
+        Ok(SelfEncrypt {
+            version,
+            cipher,
+            key_id: 0,
+            key,
+            old_key: None,
+        })
+    }
+
+    fn make_aead(&self, k: &SymKey, salt: &[u8]) -> Res<Aead> {
+        debug_assert_eq!(salt.len(), SelfEncrypt::SALT_LENGTH);
+        let salt = hkdf::import_key(self.version, self.cipher, salt)?;
+        let secret = hkdf::extract(self.version, self.cipher, Some(&salt), k)?;
+        Aead::new(self.version, self.cipher, &secret, "neqo self")
+    }
+
+    /// Rotate keys.  This causes any previous key that is being held to be replaced by the current key.
+    pub fn rotate(&mut self) -> Res<()> {
+        let sz = hkdf::key_size(self.version, self.cipher)?;
+        let new_key = hkdf::generate_key(self.version, self.cipher, sz)?;
+        self.old_key = Some(mem::replace(&mut self.key, new_key));
+        let (kid, _) = self.key_id.overflowing_add(1);
+        self.key_id = kid;
+        qinfo!(["SelfEncrypt"], "Rotated keys to {}", self.key_id);
+        Ok(())
+    }
+
+    /// Seal an item using the underlying key.  This produces a single buffer that contains
+    /// the encrypted `plaintext`, plus a version number and salt.
+    /// `aad` is only used as input to the AEAD, it is not included in the output; the
+    /// caller is responsible for carrying the AAD as appropriate.
+    pub fn seal(&self, aad: &[u8], plaintext: &[u8]) -> Res<Vec<u8>> {
+        // Format is:
+        // struct {
+        //   uint8 version;
+        //   uint8 key_id;
+        //   uint8 salt[16];
+        //   opaque aead_encrypted(plaintext)[length as expanded];
+        // };
+        // AAD covers the entire header, plus the value of the AAD parameter that is provided.
+        let salt = random(SelfEncrypt::SALT_LENGTH)?;
+        let aead = self.make_aead(&self.key, &salt)?;
+        let encoded_len = 2 + salt.len() + plaintext.len() + aead.expansion();
+
+        let mut enc = Encoder::with_capacity(encoded_len);
+        enc.encode_byte(SelfEncrypt::VERSION);
+        enc.encode_byte(self.key_id);
+        enc.encode(&salt);
+
+        let mut extended_aad = enc.clone();
+        extended_aad.encode(aad);
+
+        let offset = enc.len();
+        let mut output: Vec<u8> = enc.into();
+        output.resize(encoded_len, 0);
+        aead.encrypt(0, &extended_aad, plaintext, &mut output[offset..])?;
+        qtrace!(
+            ["SelfEncrypt"],
+            "seal {} {} -> {}",
+            hex(aad),
+            hex(plaintext),
+            hex(&output)
+        );
+        Ok(output)
+    }
+
+    fn select_key(&self, kid: u8) -> Option<&SymKey> {
+        if kid == self.key_id {
+            Some(&self.key)
+        } else {
+            let (prev_key_id, _) = self.key_id.overflowing_sub(1);
+            if kid == prev_key_id {
+                self.old_key.as_ref()
+            } else {
+                None
+            }
+        }
+    }
+
+    /// Open the protected `ciphertext`.
+    pub fn open(&self, aad: &[u8], ciphertext: &[u8]) -> Res<Vec<u8>> {
+        if ciphertext[0] != SelfEncrypt::VERSION {
+            return Err(Error::SelfEncryptFailure);
+        }
+        let key = if let Some(k) = self.select_key(ciphertext[1]) {
+            k
+        } else {
+            return Err(Error::SelfEncryptFailure);
+        };
+        let offset = 2 + SelfEncrypt::SALT_LENGTH;
+
+        let mut extended_aad = Encoder::with_capacity(offset + aad.len());
+        extended_aad.encode(&ciphertext[0..offset]);
+        extended_aad.encode(aad);
+
+        let aead = self.make_aead(key, &ciphertext[2..offset])?;
+        // NSS insists on having extra space available for decryption.
+        let padded_len = ciphertext.len() - offset;
+        let mut output = vec![0; padded_len];
+        let decrypted = aead.decrypt(0, &extended_aad, &ciphertext[offset..], &mut output)?;
+        let final_len = decrypted.len();
+        output.truncate(final_len);
+        qtrace!(
+            ["SelfEncrypt"],
+            "open {} {} -> {}",
+            hex(aad),
+            hex(ciphertext),
+            hex(&output)
+        );
+        Ok(output)
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/ssl.rs
@@ -0,0 +1,130 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![allow(dead_code)]
+#![allow(non_upper_case_globals)]
+#![allow(non_snake_case)]
+#![allow(clippy::cognitive_complexity)]
+
+use crate::constants::*;
+
+use std::os::raw::{c_uint, c_void};
+
+include!(concat!(env!("OUT_DIR"), "/nss_ssl.rs"));
+mod SSLOption {
+    include!(concat!(env!("OUT_DIR"), "/nss_sslopt.rs"));
+}
+
+// I clearly don't understand how bindgen operates.
+#[allow(clippy::empty_enum)]
+pub enum PLArenaPool {}
+#[allow(clippy::empty_enum)]
+pub enum PRFileDesc {}
+
+// Remap some constants.
+pub const SECSuccess: SECStatus = _SECStatus_SECSuccess;
+pub const SECFailure: SECStatus = _SECStatus_SECFailure;
+
+#[derive(Debug, Copy, Clone)]
+pub enum Opt {
+    Locking,
+    Tickets,
+    OcspStapling,
+    Alpn,
+    ExtendedMasterSecret,
+    SignedCertificateTimestamps,
+    EarlyData,
+    RecordSizeLimit,
+    Tls13CompatMode,
+    HelloDowngradeCheck,
+}
+
+impl Opt {
+    // Cast is safe here because SSLOptions are within the i32 range
+    #[allow(clippy::cast_possible_wrap)]
+    pub fn as_int(self) -> PRInt32 {
+        let i = match self {
+            Opt::Locking => SSLOption::SSL_NO_LOCKS,
+            Opt::Tickets => SSLOption::SSL_ENABLE_SESSION_TICKETS,
+            Opt::OcspStapling => SSLOption::SSL_ENABLE_OCSP_STAPLING,
+            Opt::Alpn => SSLOption::SSL_ENABLE_ALPN,
+            Opt::ExtendedMasterSecret => SSLOption::SSL_ENABLE_EXTENDED_MASTER_SECRET,
+            Opt::SignedCertificateTimestamps => SSLOption::SSL_ENABLE_SIGNED_CERT_TIMESTAMPS,
+            Opt::EarlyData => SSLOption::SSL_ENABLE_0RTT_DATA,
+            Opt::RecordSizeLimit => SSLOption::SSL_RECORD_SIZE_LIMIT,
+            Opt::Tls13CompatMode => SSLOption::SSL_ENABLE_TLS13_COMPAT_MODE,
+            Opt::HelloDowngradeCheck => SSLOption::SSL_ENABLE_HELLO_DOWNGRADE_CHECK,
+        };
+        i as PRInt32
+    }
+
+    // Some options are backwards, like SSL_NO_LOCKS, so use this to manage that.
+    pub fn map_enabled(self, enabled: bool) -> PRIntn {
+        let v = match self {
+            Opt::Locking => !enabled,
+            _ => enabled,
+        };
+        PRIntn::from(v)
+    }
+}
+
+experimental_api!(SSL_GetCurrentEpoch(
+    fd: *mut PRFileDesc,
+    read_epoch: *mut u16,
+    write_epoch: *mut u16,
+));
+experimental_api!(SSL_HelloRetryRequestCallback(
+    fd: *mut PRFileDesc,
+    cb: SSLHelloRetryRequestCallback,
+    arg: *mut c_void,
+));
+experimental_api!(SSL_RecordLayerWriteCallback(
+    fd: *mut PRFileDesc,
+    cb: SSLRecordWriteCallback,
+    arg: *mut c_void,
+));
+experimental_api!(SSL_RecordLayerData(
+    fd: *mut PRFileDesc,
+    epoch: Epoch,
+    ct: SSLContentType::Type,
+    data: *const u8,
+    len: c_uint,
+));
+experimental_api!(SSL_SendSessionTicket(
+    fd: *mut PRFileDesc,
+    extra: *const u8,
+    len: c_uint,
+));
+experimental_api!(SSL_SetMaxEarlyDataSize(fd: *mut PRFileDesc, size: u32));
+experimental_api!(SSL_SetResumptionToken(
+    fd: *mut PRFileDesc,
+    token: *const u8,
+    len: c_uint,
+));
+experimental_api!(SSL_SetResumptionTokenCallback(
+    fd: *mut PRFileDesc,
+    cb: SSLResumptionTokenCallback,
+    arg: *mut c_void,
+));
+experimental_api!(SSL_SetTimeFunc(
+    fd: *mut PRFileDesc,
+    cb: SSLTimeFunc,
+    arg: *mut c_void,
+));
+
+#[cfg(test)]
+mod tests {
+    use super::{SSL_GetNumImplementedCiphers, SSL_NumImplementedCiphers};
+
+    #[test]
+    fn num_ciphers() {
+        assert!(unsafe { SSL_NumImplementedCiphers } > 0);
+        assert!(unsafe { SSL_GetNumImplementedCiphers() } > 0);
+        assert_eq!(unsafe { SSL_NumImplementedCiphers }, unsafe {
+            SSL_GetNumImplementedCiphers()
+        });
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/time.rs
@@ -0,0 +1,209 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::err::{Error, Res};
+
+use neqo_common::once::OnceResult;
+
+use std::convert::{TryFrom, TryInto};
+use std::ops::Deref;
+use std::time::{Duration, Instant};
+
+include!(concat!(env!("OUT_DIR"), "/nspr_time.rs"));
+
+/// This struct holds the zero time used for converting between `Instant` and `PRTime`.
+#[derive(Debug)]
+struct TimeZero {
+    instant: Instant,
+    prtime: PRTime,
+}
+
+impl TimeZero {
+    /// This function sets a baseline from an instance of `Instant`.
+    /// This allows for the possibility that code that uses these APIs will create
+    /// instances of `Instant` before any of this code is run.  If `Instant`s older than
+    /// `BASE_TIME` are used with these conversion functions, they will fail.
+    /// To avoid that, we make sure that this sets the base time using the first value
+    /// it sees if it is in the past.  If it is not, then use `Instant::now()` instead.
+    pub fn baseline(t: Instant) -> Self {
+        let now = Instant::now();
+        let prnow = unsafe { PR_Now() };
+
+        if now <= t {
+            // `t` is in the future, just use `now`.
+            Self {
+                instant: now,
+                prtime: prnow,
+            }
+        } else {
+            let elapsed = Interval::from(now.duration_since(now));
+            // An error from these unwrap functions would require
+            // ridiculously long application running time.
+            let prelapsed: PRTime = elapsed.try_into().unwrap();
+            Self {
+                instant: t,
+                prtime: prnow.checked_sub(prelapsed).unwrap(),
+            }
+        }
+    }
+}
+
+static mut BASE_TIME: OnceResult<TimeZero> = OnceResult::new();
+
+fn get_base() -> &'static TimeZero {
+    let f = || TimeZero {
+        instant: Instant::now(),
+        prtime: unsafe { PR_Now() },
+    };
+    unsafe { BASE_TIME.call_once(f) }
+}
+
+pub(crate) fn init() {
+    let _ = get_base();
+}
+
+/// Time wraps Instant and provides conversion functions into `PRTime`.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct Time {
+    t: Instant,
+}
+
+impl Deref for Time {
+    type Target = Instant;
+    fn deref(&self) -> &Self::Target {
+        &self.t
+    }
+}
+
+impl From<Instant> for Time {
+    /// Convert from an Instant into a Time.
+    fn from(t: Instant) -> Self {
+        // Call `TimeZero::baseline(t)` so that time zero can be set.
+        let f = || TimeZero::baseline(t);
+        let _ = unsafe { BASE_TIME.call_once(f) };
+        Self { t }
+    }
+}
+
+impl TryFrom<PRTime> for Time {
+    type Error = Error;
+    fn try_from(prtime: PRTime) -> Res<Self> {
+        let base = get_base();
+        if let Some(delta) = prtime.checked_sub(base.prtime) {
+            let d = Duration::from_micros(delta.try_into()?);
+            if let Some(t) = base.instant.checked_add(d) {
+                Ok(Self { t })
+            } else {
+                Err(Error::TimeTravelError)
+            }
+        } else {
+            Err(Error::TimeTravelError)
+        }
+    }
+}
+
+impl TryInto<PRTime> for Time {
+    type Error = Error;
+    fn try_into(self) -> Res<PRTime> {
+        let base = get_base();
+        // TODO(mt) use checked_duration_since when that is available.
+        let delta = self.t.duration_since(base.instant);
+        if let Ok(d) = PRTime::try_from(delta.as_micros()) {
+            if let Some(v) = d.checked_add(base.prtime) {
+                Ok(v)
+            } else {
+                Err(Error::TimeTravelError)
+            }
+        } else {
+            Err(Error::TimeTravelError)
+        }
+    }
+}
+
+impl Into<Instant> for Time {
+    fn into(self) -> Instant {
+        self.t
+    }
+}
+
+/// Interval wraps Duration and provides conversion functions into `PRTime`.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct Interval {
+    d: Duration,
+}
+
+impl Deref for Interval {
+    type Target = Duration;
+    fn deref(&self) -> &Self::Target {
+        &self.d
+    }
+}
+
+impl TryFrom<PRTime> for Interval {
+    type Error = Error;
+    fn try_from(prtime: PRTime) -> Res<Self> {
+        Ok(Self {
+            d: Duration::from_micros(u64::try_from(prtime)?),
+        })
+    }
+}
+
+impl From<Duration> for Interval {
+    fn from(d: Duration) -> Self {
+        Self { d }
+    }
+}
+
+impl TryInto<PRTime> for Interval {
+    type Error = Error;
+    fn try_into(self) -> Res<PRTime> {
+        Ok(PRTime::try_from(self.d.as_micros())?)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn convert_stable() {
+        init();
+        let now = Time::from(Instant::now());
+        let pr: PRTime = now.try_into().expect("convert to PRTime with truncation");
+        let t2 = Time::try_from(pr).expect("convert to Instant");
+        let pr2: PRTime = t2.try_into().expect("convert to PRTime again");
+        assert_eq!(pr, pr2);
+        let t3 = Time::try_from(pr2).expect("convert to Instant again");
+        assert_eq!(t2, t3);
+    }
+
+    #[test]
+    fn past_time() {
+        init();
+        let base = get_base();
+        assert!(Time::try_from(base.prtime - 1).is_err());
+    }
+
+    #[test]
+    fn negative_time() {
+        init();
+        assert!(Time::try_from(-1).is_err());
+    }
+
+    #[test]
+    fn negative_interval() {
+        init();
+        assert!(Interval::try_from(-1).is_err());
+    }
+
+    #[test]
+    fn overflow_interval() {
+        init();
+        let interval = Interval::from(Duration::from_micros(std::u64::MAX));
+        let res: Res<PRTime> = interval.try_into();
+        assert!(res.is_err());
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/tests/aead.rs
@@ -0,0 +1,115 @@
+#![deny(warnings)]
+
+use neqo_crypto::aead::Aead;
+use neqo_crypto::constants::*;
+use neqo_crypto::hkdf;
+use test_fixture::fixture_init;
+
+const AAD: &[u8] = &[
+    0xc1, 0xff, 0x00, 0x00, 0x12, 0x05, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, 0x00, 0x40,
+    0x74, 0x00, 0x01,
+];
+const PLAINTEXT: &[u8] = &[
+    0x0d, 0x00, 0x00, 0x00, 0x00, 0x18, 0x41, 0x0a, 0x02, 0x00, 0x00, 0x56, 0x03, 0x03, 0xee, 0xfc,
+    0xe7, 0xf7, 0xb3, 0x7b, 0xa1, 0xd1, 0x63, 0x2e, 0x96, 0x67, 0x78, 0x25, 0xdd, 0xf7, 0x39, 0x88,
+    0xcf, 0xc7, 0x98, 0x25, 0xdf, 0x56, 0x6d, 0xc5, 0x43, 0x0b, 0x9a, 0x04, 0x5a, 0x12, 0x00, 0x13,
+    0x01, 0x00, 0x00, 0x2e, 0x00, 0x33, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x9d, 0x3c, 0x94, 0x0d,
+    0x89, 0x69, 0x0b, 0x84, 0xd0, 0x8a, 0x60, 0x99, 0x3c, 0x14, 0x4e, 0xca, 0x68, 0x4d, 0x10, 0x81,
+    0x28, 0x7c, 0x83, 0x4d, 0x53, 0x11, 0xbc, 0xf3, 0x2b, 0xb9, 0xda, 0x1a, 0x00, 0x2b, 0x00, 0x02,
+    0x03, 0x04,
+];
+
+fn make_aead(cipher: Cipher) -> Aead {
+    fixture_init();
+
+    let secret = hkdf::import_key(
+        TLS_VERSION_1_3,
+        cipher,
+        &[
+            0x47, 0xb2, 0xea, 0xea, 0x6c, 0x26, 0x6e, 0x32, 0xc0, 0x69, 0x7a, 0x9e, 0x2a, 0x89,
+            0x8b, 0xdf, 0x5c, 0x4f, 0xb3, 0xe5, 0xac, 0x34, 0xf0, 0xe5, 0x49, 0xbf, 0x2c, 0x58,
+            0x58, 0x1a, 0x38, 0x11,
+        ],
+    )
+    .expect("make a secret");
+    Aead::new(
+        TLS_VERSION_1_3,
+        cipher,
+        &secret,
+        "quic ", // Note the trailing space here.
+    )
+    .expect("can make an AEAD")
+}
+
+#[test]
+fn aead_encrypt_decrypt() {
+    const TOGGLE: u8 = 77;
+    let aead = make_aead(TLS_AES_128_GCM_SHA256);
+    let ciphertext_buf = &mut [0; 1024]; // Can't use PLAINTEXT.len() here.
+    let ciphertext = aead
+        .encrypt(1, AAD, PLAINTEXT, ciphertext_buf)
+        .expect("encrypt should work");
+    let expected_ciphertext: &[u8] = &[
+        0x5f, 0x01, 0xc4, 0xc2, 0xa2, 0x30, 0x3d, 0x29, 0x7e, 0x3c, 0x51, 0x9b, 0xf6, 0xb2, 0x23,
+        0x86, 0xe3, 0xd0, 0xbd, 0x6d, 0xfc, 0x66, 0x12, 0x16, 0x77, 0x29, 0x80, 0x31, 0x04, 0x1b,
+        0xb9, 0xa7, 0x9c, 0x9f, 0x0f, 0x9d, 0x4c, 0x58, 0x77, 0x27, 0x0a, 0x66, 0x0f, 0x5d, 0xa3,
+        0x62, 0x07, 0xd9, 0x8b, 0x73, 0x83, 0x9b, 0x2f, 0xdf, 0x2e, 0xf8, 0xe7, 0xdf, 0x5a, 0x51,
+        0xb1, 0x7b, 0x8c, 0x68, 0xd8, 0x64, 0xfd, 0x3e, 0x70, 0x8c, 0x6c, 0x1b, 0x71, 0xa9, 0x8a,
+        0x33, 0x18, 0x15, 0x59, 0x9e, 0xf5, 0x01, 0x4e, 0xa3, 0x8c, 0x44, 0xbd, 0xfd, 0x38, 0x7c,
+        0x03, 0xb5, 0x27, 0x5c, 0x35, 0xe0, 0x09, 0xb6, 0x23, 0x8f, 0x83, 0x14, 0x20, 0x04, 0x7c,
+        0x72, 0x71, 0x28, 0x1c, 0xcb, 0x54, 0xdf, 0x78, 0x84,
+    ];
+    assert_eq!(ciphertext, expected_ciphertext);
+
+    let plaintext_buf = &mut [0; 1024]; // Can't use PLAINTEXT.len() here.
+    let plaintext = aead
+        .decrypt(1, AAD, ciphertext, plaintext_buf)
+        .expect("decrypt should also work");
+    assert_eq!(plaintext, PLAINTEXT);
+
+    // Decryption failures...
+    // Different counter.
+    let res = aead.decrypt(2, AAD, ciphertext, plaintext_buf);
+    assert!(res.is_err());
+
+    // Front-truncate ciphertext.
+    let res = aead.decrypt(1, AAD, &ciphertext[1..], plaintext_buf);
+    assert!(res.is_err());
+
+    // End-truncate ciphertext.
+    let ciphertext_last = ciphertext.len() - 1;
+    let res = aead.decrypt(1, AAD, &ciphertext[..ciphertext_last], plaintext_buf);
+    assert!(res.is_err());
+
+    // Mess with the buffer.
+    let mut scratch = Vec::new();
+    scratch.extend_from_slice(ciphertext);
+
+    // Toggle first octet.
+    scratch[0] ^= TOGGLE;
+    let res = aead.decrypt(1, AAD, &scratch[..], plaintext_buf);
+    assert!(res.is_err());
+
+    // Toggle the auth tag.
+    scratch[0] ^= TOGGLE;
+    scratch[ciphertext_last] ^= TOGGLE;
+    let res = aead.decrypt(1, AAD, &scratch[..], plaintext_buf);
+    assert!(res.is_err());
+
+    // Mess with the AAD.
+    scratch.clear();
+    scratch.extend_from_slice(AAD);
+
+    // Front-truncate.
+    let res = aead.decrypt(1, &scratch[1..], ciphertext, plaintext_buf);
+    assert!(res.is_err());
+
+    // End-truncate.
+    let aad_last = AAD.len() - 1;
+    let res = aead.decrypt(1, &scratch[..aad_last], ciphertext, plaintext_buf);
+    assert!(res.is_err());
+
+    scratch[0] ^= TOGGLE;
+    let res = aead.decrypt(1, &scratch[..], ciphertext, plaintext_buf);
+    assert!(res.is_err());
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/tests/agent.rs
@@ -0,0 +1,353 @@
+#![deny(warnings)]
+
+use neqo_crypto::*;
+
+mod handshake;
+use crate::handshake::*;
+use test_fixture::{fixture_init, now};
+
+#[test]
+fn make_client() {
+    fixture_init();
+    let _c = Client::new("server").expect("should create client");
+}
+
+#[test]
+fn make_server() {
+    fixture_init();
+    let _s = Server::new(&["key"]).expect("should create server");
+}
+
+#[test]
+fn basic() {
+    fixture_init();
+    let mut client = Client::new("server.example").expect("should create client");
+    println!("client {:p}", &client);
+    let mut server = Server::new(&["key"]).expect("should create server");
+    println!("server {:p}", &server);
+
+    let bytes = client.handshake(now(), &[]).expect("send CH");
+    assert!(!bytes.is_empty());
+    assert_eq!(*client.state(), HandshakeState::InProgress);
+
+    let bytes = server
+        .handshake(now(), &bytes[..])
+        .expect("read CH, send SH");
+    assert!(!bytes.is_empty());
+    assert_eq!(*server.state(), HandshakeState::InProgress);
+
+    let bytes = client.handshake(now(), &bytes[..]).expect("send CF");
+    assert!(bytes.is_empty());
+    assert_eq!(*client.state(), HandshakeState::AuthenticationPending);
+
+    client.authenticated(AuthenticationStatus::Ok);
+    assert_eq!(*client.state(), HandshakeState::Authenticated(0));
+
+    // Calling handshake() again indicates that we're happy with the cert.
+    let bytes = client.handshake(now(), &[]).expect("send CF");
+    assert!(!bytes.is_empty());
+    assert!(client.state().connected());
+
+    let client_info = client.info().expect("got info");
+    assert_eq!(TLS_VERSION_1_3, client_info.version());
+    assert_eq!(TLS_AES_128_GCM_SHA256, client_info.cipher_suite());
+
+    let bytes = server.handshake(now(), &bytes[..]).expect("finish");
+    assert!(bytes.is_empty());
+    assert!(server.state().connected());
+
+    let server_info = server.info().expect("got info");
+    assert_eq!(TLS_VERSION_1_3, server_info.version());
+    assert_eq!(TLS_AES_128_GCM_SHA256, server_info.cipher_suite());
+}
+
+fn check_client_preinfo(client_preinfo: SecretAgentPreInfo) {
+    assert_eq!(client_preinfo.version(), None);
+    assert_eq!(client_preinfo.cipher_suite(), None);
+    assert_eq!(client_preinfo.early_data(), false);
+    assert_eq!(client_preinfo.early_data_cipher(), None);
+    assert_eq!(client_preinfo.max_early_data(), 0);
+    assert_eq!(client_preinfo.alpn(), None);
+}
+
+fn check_server_preinfo(server_preinfo: SecretAgentPreInfo) {
+    assert_eq!(server_preinfo.version(), Some(TLS_VERSION_1_3));
+    assert_eq!(server_preinfo.cipher_suite(), Some(TLS_AES_128_GCM_SHA256));
+    assert_eq!(server_preinfo.early_data(), false);
+    assert_eq!(server_preinfo.early_data_cipher(), None);
+    assert_eq!(server_preinfo.max_early_data(), 0);
+    assert_eq!(server_preinfo.alpn(), None);
+}
+
+#[test]
+fn raw() {
+    fixture_init();
+    let mut client = Client::new("server.example").expect("should create client");
+    println!("client {:?}", client);
+    let mut server = Server::new(&["key"]).expect("should create server");
+    println!("server {:?}", server);
+
+    let client_records = client.handshake_raw(now(), None).expect("send CH");
+    assert!(!client_records.is_empty());
+    assert_eq!(*client.state(), HandshakeState::InProgress);
+
+    check_client_preinfo(client.preinfo().expect("get preinfo"));
+
+    let server_records =
+        forward_records(now(), &mut server, client_records).expect("read CH, send SH");
+    assert!(!server_records.is_empty());
+    assert_eq!(*server.state(), HandshakeState::InProgress);
+
+    check_server_preinfo(server.preinfo().expect("get preinfo"));
+
+    let client_records = forward_records(now(), &mut client, server_records).expect("send CF");
+    assert!(client_records.is_empty());
+    assert_eq!(*client.state(), HandshakeState::AuthenticationPending);
+
+    client.authenticated(AuthenticationStatus::Ok);
+    assert_eq!(*client.state(), HandshakeState::Authenticated(0));
+
+    // Calling handshake() again indicates that we're happy with the cert.
+    let client_records = client.handshake_raw(now(), None).expect("send CF");
+    assert!(!client_records.is_empty());
+    assert!(client.state().connected());
+
+    let server_records = forward_records(now(), &mut server, client_records).expect("finish");
+    assert!(server_records.is_empty());
+    assert!(server.state().connected());
+
+    // The client should have one certificate for the server.
+    let mut certs = client.peer_certificate().unwrap();
+    let cert_vec: Vec<&[u8]> = certs.collect();
+    assert_eq!(1, cert_vec.len());
+
+    // The server shouldn't have a client certificate.
+    assert!(server.peer_certificate().is_none());
+}
+
+#[test]
+fn chacha_client() {
+    fixture_init();
+    let mut client = Client::new("server.example").expect("should create client");
+    let mut server = Server::new(&["key"]).expect("should create server");
+    client
+        .enable_ciphers(&[TLS_CHACHA20_POLY1305_SHA256])
+        .expect("ciphers set");
+
+    connect(&mut client, &mut server);
+
+    assert_eq!(
+        client.info().unwrap().cipher_suite(),
+        TLS_CHACHA20_POLY1305_SHA256
+    );
+    assert_eq!(
+        server.info().unwrap().cipher_suite(),
+        TLS_CHACHA20_POLY1305_SHA256
+    );
+}
+
+#[test]
+fn p256_server() {
+    fixture_init();
+    let mut client = Client::new("server.example").expect("should create client");
+    let mut server = Server::new(&["key"]).expect("should create server");
+    server
+        .set_groups(&[TLS_GRP_EC_SECP256R1])
+        .expect("groups set");
+
+    connect(&mut client, &mut server);
+
+    assert_eq!(client.info().unwrap().key_exchange(), TLS_GRP_EC_SECP256R1);
+    assert_eq!(server.info().unwrap().key_exchange(), TLS_GRP_EC_SECP256R1);
+}
+
+#[test]
+fn alpn() {
+    fixture_init();
+    let mut client = Client::new("server.example").expect("should create client");
+    client.set_alpn(&["alpn"]).expect("should set ALPN");
+    let mut server = Server::new(&["key"]).expect("should create server");
+    server.set_alpn(&["alpn"]).expect("should set ALPN");
+
+    connect(&mut client, &mut server);
+
+    let expected = Some(String::from("alpn"));
+    assert_eq!(expected.as_ref(), client.info().unwrap().alpn());
+    assert_eq!(expected.as_ref(), server.info().unwrap().alpn());
+}
+
+#[test]
+fn alpn_multi() {
+    fixture_init();
+    let mut client = Client::new("server.example").expect("should create client");
+    client
+        .set_alpn(&["dummy", "alpn"])
+        .expect("should set ALPN");
+    let mut server = Server::new(&["key"]).expect("should create server");
+    server
+        .set_alpn(&["alpn", "other"])
+        .expect("should set ALPN");
+
+    connect(&mut client, &mut server);
+
+    let expected = Some(String::from("alpn"));
+    assert_eq!(expected.as_ref(), client.info().unwrap().alpn());
+    assert_eq!(expected.as_ref(), server.info().unwrap().alpn());
+}
+
+#[test]
+fn alpn_server_pref() {
+    fixture_init();
+    let mut client = Client::new("server.example").expect("should create client");
+    client
+        .set_alpn(&["dummy", "alpn"])
+        .expect("should set ALPN");
+    let mut server = Server::new(&["key"]).expect("should create server");
+    server
+        .set_alpn(&["alpn", "dummy"])
+        .expect("should set ALPN");
+
+    connect(&mut client, &mut server);
+
+    let expected = Some(String::from("alpn"));
+    assert_eq!(expected.as_ref(), client.info().unwrap().alpn());
+    assert_eq!(expected.as_ref(), server.info().unwrap().alpn());
+}
+
+#[test]
+fn alpn_no_protocol() {
+    fixture_init();
+    let mut client = Client::new("server.example").expect("should create client");
+    client.set_alpn(&["a"]).expect("should set ALPN");
+    let mut server = Server::new(&["key"]).expect("should create server");
+    server.set_alpn(&["b"]).expect("should set ALPN");
+
+    connect_fail(&mut client, &mut server);
+
+    // TODO(mt) check the error code
+}
+
+#[test]
+fn alpn_client_only() {
+    fixture_init();
+    let mut client = Client::new("server.example").expect("should create client");
+    client.set_alpn(&["alpn"]).expect("should set ALPN");
+    let mut server = Server::new(&["key"]).expect("should create server");
+
+    connect(&mut client, &mut server);
+
+    assert_eq!(None, client.info().unwrap().alpn());
+    assert_eq!(None, server.info().unwrap().alpn());
+}
+
+#[test]
+fn alpn_server_only() {
+    fixture_init();
+    let mut client = Client::new("server.example").expect("should create client");
+    let mut server = Server::new(&["key"]).expect("should create server");
+    server.set_alpn(&["alpn"]).expect("should set ALPN");
+
+    connect(&mut client, &mut server);
+
+    assert_eq!(None, client.info().unwrap().alpn());
+    assert_eq!(None, server.info().unwrap().alpn());
+}
+
+#[test]
+fn resume() {
+    let (_, token) = resumption_setup(Resumption::WithoutZeroRtt);
+
+    let mut client = Client::new("server.example").expect("should create second client");
+    let mut server = Server::new(&["key"]).expect("should create second server");
+
+    client
+        .set_resumption_token(&token[..])
+        .expect("should accept token");
+    connect(&mut client, &mut server);
+
+    assert!(client.info().unwrap().resumed());
+    assert!(server.info().unwrap().resumed());
+}
+
+#[test]
+fn zero_rtt() {
+    let (anti_replay, token) = resumption_setup(Resumption::WithZeroRtt);
+
+    // Finally, 0-RTT should succeed.
+    let mut client = Client::new("server.example").expect("should create client");
+    let mut server = Server::new(&["key"]).expect("should create server");
+    client
+        .set_resumption_token(&token[..])
+        .expect("should accept token");
+    client.enable_0rtt().expect("should enable 0-RTT");
+    server
+        .enable_0rtt(
+            anti_replay.as_ref().unwrap(),
+            0xffff_ffff,
+            PermissiveZeroRttChecker::make(),
+        )
+        .expect("should enable 0-RTT");
+
+    connect(&mut client, &mut server);
+    assert!(client.info().unwrap().early_data_accepted());
+    assert!(server.info().unwrap().early_data_accepted());
+}
+
+#[test]
+fn zero_rtt_no_eoed() {
+    let (anti_replay, token) = resumption_setup(Resumption::WithZeroRtt);
+
+    // Finally, 0-RTT should succeed.
+    let mut client = Client::new("server.example").expect("should create client");
+    let mut server = Server::new(&["key"]).expect("should create server");
+    client
+        .set_resumption_token(&token[..])
+        .expect("should accept token");
+    client.enable_0rtt().expect("should enable 0-RTT");
+    client.disable_end_of_early_data();
+    server
+        .enable_0rtt(
+            anti_replay.as_ref().unwrap(),
+            0xffff_ffff,
+            PermissiveZeroRttChecker::make(),
+        )
+        .expect("should enable 0-RTT");
+    server.disable_end_of_early_data();
+
+    connect(&mut client, &mut server);
+    assert!(client.info().unwrap().early_data_accepted());
+    assert!(server.info().unwrap().early_data_accepted());
+}
+
+#[derive(Debug)]
+struct RejectZeroRtt {}
+impl ZeroRttChecker for RejectZeroRtt {
+    fn check(&self, token: &[u8]) -> ZeroRttCheckResult {
+        assert_eq!(ZERO_RTT_TOKEN_DATA, token);
+        ZeroRttCheckResult::Reject
+    }
+}
+
+#[test]
+fn reject_zero_rtt() {
+    let (anti_replay, token) = resumption_setup(Resumption::WithZeroRtt);
+
+    // Finally, 0-RTT should succeed.
+    let mut client = Client::new("server.example").expect("should create client");
+    let mut server = Server::new(&["key"]).expect("should create server");
+    client
+        .set_resumption_token(&token[..])
+        .expect("should accept token");
+    client.enable_0rtt().expect("should enable 0-RTT");
+    server
+        .enable_0rtt(
+            anti_replay.as_ref().unwrap(),
+            0xffff_ffff,
+            Box::new(RejectZeroRtt {}),
+        )
+        .expect("should enable 0-RTT");
+
+    connect(&mut client, &mut server);
+    assert!(!client.info().unwrap().early_data_accepted());
+    assert!(!server.info().unwrap().early_data_accepted());
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/tests/ext.rs
@@ -0,0 +1,94 @@
+#![deny(warnings)]
+
+use neqo_crypto::*;
+use std::cell::RefCell;
+use std::rc::Rc;
+use test_fixture::fixture_init;
+
+mod handshake;
+use crate::handshake::*;
+
+struct NoopExtensionHandler;
+impl ExtensionHandler for NoopExtensionHandler {}
+
+// This test just handshakes.  It doesn't really do anything about capturing the
+#[test]
+fn noop_extension_handler() {
+    fixture_init();
+    let mut client = Client::new("server.example").expect("should create client");
+    let mut server = Server::new(&["key"]).expect("should create server");
+
+    client
+        .extension_handler(0xffff, Rc::new(RefCell::new(NoopExtensionHandler)))
+        .expect("installed");
+    server
+        .extension_handler(0xffff, Rc::new(RefCell::new(NoopExtensionHandler)))
+        .expect("installed");
+
+    connect(&mut client, &mut server);
+}
+
+#[derive(Debug, Default)]
+struct SimpleExtensionHandler {
+    written: bool,
+    handled: bool,
+}
+
+impl SimpleExtensionHandler {
+    #[allow(dead_code)]
+    pub fn negotiated(&self) -> bool {
+        self.written && self.handled
+    }
+}
+
+impl ExtensionHandler for SimpleExtensionHandler {
+    fn write(&mut self, msg: HandshakeMessage, d: &mut [u8]) -> ExtensionWriterResult {
+        match msg {
+            TLS_HS_CLIENT_HELLO | TLS_HS_ENCRYPTED_EXTENSIONS => {
+                self.written = true;
+                d[0] = 77;
+                ExtensionWriterResult::Write(1)
+            }
+            _ => ExtensionWriterResult::Skip,
+        }
+    }
+
+    fn handle(&mut self, msg: HandshakeMessage, d: &[u8]) -> ExtensionHandlerResult {
+        match msg {
+            TLS_HS_CLIENT_HELLO | TLS_HS_ENCRYPTED_EXTENSIONS => {
+                self.handled = true;
+                if d.len() != 1 {
+                    ExtensionHandlerResult::Alert(50) // decode_error
+                } else if d[0] != 77 {
+                    ExtensionHandlerResult::Alert(47) // illegal_parameter
+                } else {
+                    ExtensionHandlerResult::Ok
+                }
+            }
+            _ => ExtensionHandlerResult::Alert(110), // unsupported_extension
+        }
+    }
+}
+
+#[test]
+fn simple_extension() {
+    fixture_init();
+    let mut client = Client::new("server.example").expect("should create client");
+    let mut server = Server::new(&["key"]).expect("should create server");
+
+    let client_handler = Rc::new(RefCell::new(SimpleExtensionHandler::default()));
+    let ch2 = Rc::clone(&client_handler);
+    client
+        .extension_handler(0xffff, ch2)
+        .expect("client handler installed");
+    let server_handler = Rc::new(RefCell::new(SimpleExtensionHandler::default()));
+    let sh2 = Rc::clone(&server_handler);
+    server
+        .extension_handler(0xffff, sh2)
+        .expect("server handler installed");
+
+    connect(&mut client, &mut server);
+
+    assert!(client_handler.borrow().negotiated());
+    assert!(server_handler.borrow().negotiated());
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/tests/handshake.rs
@@ -0,0 +1,150 @@
+#![allow(dead_code)]
+
+use neqo_common::qinfo;
+use neqo_crypto::*;
+use std::mem;
+use std::time::Instant;
+use test_fixture::{anti_replay, fixture_init, now};
+
+pub fn forward_records(
+    now: Instant,
+    agent: &mut SecretAgent,
+    records_in: RecordList,
+) -> Res<RecordList> {
+    let mut expected_state = match agent.state() {
+        HandshakeState::New => HandshakeState::New,
+        _ => HandshakeState::InProgress,
+    };
+    let mut records_out = RecordList::default();
+    for record in records_in.into_iter() {
+        assert_eq!(records_out.len(), 0);
+        assert_eq!(*agent.state(), expected_state);
+
+        records_out = agent.handshake_raw(now, Some(record))?;
+        expected_state = HandshakeState::InProgress;
+    }
+    Ok(records_out)
+}
+
+fn handshake(now: Instant, client: &mut SecretAgent, server: &mut SecretAgent) {
+    let mut a = client;
+    let mut b = server;
+    let mut records = a.handshake_raw(now, None).unwrap();
+    let is_done = |agent: &mut SecretAgent| match *agent.state() {
+        HandshakeState::Complete(_) | HandshakeState::Failed(_) => true,
+        _ => false,
+    };
+    while !is_done(b) {
+        records = match forward_records(now, &mut b, records) {
+            Ok(r) => r,
+            _ => {
+                // TODO(mt) take the alert generated by the failed handshake
+                // and allow it to be sent to the peer.
+                return;
+            }
+        };
+
+        if *b.state() == HandshakeState::AuthenticationPending {
+            b.authenticated(AuthenticationStatus::Ok);
+            records = b.handshake_raw(now, None).unwrap();
+        }
+        mem::swap(&mut a, &mut b);
+    }
+}
+
+pub fn connect_at(now: Instant, client: &mut SecretAgent, server: &mut SecretAgent) {
+    handshake(now, client, server);
+    qinfo!("client: {:?}", client.state());
+    qinfo!("server: {:?}", server.state());
+    assert!(client.state().connected());
+    assert!(server.state().connected());
+}
+
+pub fn connect(client: &mut SecretAgent, server: &mut SecretAgent) {
+    connect_at(now(), client, server);
+}
+
+pub fn connect_fail(client: &mut SecretAgent, server: &mut SecretAgent) {
+    handshake(now(), client, server);
+    assert!(!client.state().connected());
+    assert!(!server.state().connected());
+}
+
+#[derive(Clone, Copy, Debug)]
+pub enum Resumption {
+    WithoutZeroRtt,
+    WithZeroRtt,
+}
+
+pub const ZERO_RTT_TOKEN_DATA: &[u8] = b"zero-rtt-token";
+
+#[derive(Debug)]
+pub struct PermissiveZeroRttChecker {
+    resuming: bool,
+}
+impl PermissiveZeroRttChecker {
+    pub fn make() -> Box<dyn ZeroRttChecker> {
+        Box::new(PermissiveZeroRttChecker { resuming: true })
+    }
+}
+impl ZeroRttChecker for PermissiveZeroRttChecker {
+    fn check(&self, token: &[u8]) -> ZeroRttCheckResult {
+        if self.resuming {
+            assert_eq!(ZERO_RTT_TOKEN_DATA, token);
+        } else {
+            assert!(token.is_empty());
+        }
+        ZeroRttCheckResult::Accept
+    }
+}
+
+fn zero_rtt_setup(
+    mode: Resumption,
+    client: &mut Client,
+    server: &mut Server,
+) -> Option<AntiReplay> {
+    if let Resumption::WithZeroRtt = mode {
+        client.enable_0rtt().expect("should enable 0-RTT on client");
+
+        let anti_replay = anti_replay();
+        server
+            .enable_0rtt(
+                &anti_replay,
+                0xffff_ffff,
+                Box::new(PermissiveZeroRttChecker { resuming: false }),
+            )
+            .expect("should enable 0-RTT on server");
+        Some(anti_replay)
+    } else {
+        None
+    }
+}
+
+pub fn resumption_setup(mode: Resumption) -> (Option<AntiReplay>, Vec<u8>) {
+    fixture_init();
+
+    let mut client = Client::new("server.example").expect("should create client");
+    let mut server = Server::new(&["key"]).expect("should create server");
+    let anti_replay = zero_rtt_setup(mode, &mut client, &mut server);
+
+    connect(&mut client, &mut server);
+
+    assert!(!client.info().unwrap().resumed());
+    assert!(!server.info().unwrap().resumed());
+    assert!(!client.info().unwrap().early_data_accepted());
+    assert!(!server.info().unwrap().early_data_accepted());
+
+    let server_records = server
+        .send_ticket(now(), ZERO_RTT_TOKEN_DATA)
+        .expect("ticket sent");
+    assert_eq!(server_records.len(), 1);
+    let client_records = client
+        .handshake_raw(now(), server_records.into_iter().next())
+        .expect("records ingested");
+    assert_eq!(client_records.len(), 0);
+
+    // `client` is about to go out of scope,
+    // but we only need to keep the resumption token, so clone it.
+    let token = client.resumption_token().expect("token is present").clone();
+    (anti_replay, token)
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/tests/hkdf.rs
@@ -0,0 +1,149 @@
+#![deny(warnings)]
+
+use neqo_crypto::constants::*;
+use neqo_crypto::{hkdf, SymKey};
+use test_fixture::fixture_init;
+
+const SALT: &[u8] = &[
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+];
+
+const IKM: &[u8] = &[
+    0x01, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+];
+
+const SESSION_HASH: &[u8] = &[
+    0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
+    0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+    0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+    0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+];
+
+fn cipher_hash_len(cipher: Cipher) -> usize {
+    match cipher {
+        TLS_AES_128_GCM_SHA256 | TLS_CHACHA20_POLY1305_SHA256 => 32,
+        TLS_AES_256_GCM_SHA384 => 48,
+        _ => unreachable!(),
+    }
+}
+
+fn import_keys(cipher: Cipher) -> (SymKey, SymKey) {
+    let l = cipher_hash_len(cipher);
+    (
+        hkdf::import_key(TLS_VERSION_1_3, cipher, &SALT[0..l]).expect("import salt"),
+        hkdf::import_key(TLS_VERSION_1_3, cipher, &IKM[0..l]).expect("import IKM"),
+    )
+}
+
+fn extract(cipher: Cipher, expected: &[u8]) {
+    fixture_init();
+    let (salt, ikm) = import_keys(cipher);
+    let prk = hkdf::extract(TLS_VERSION_1_3, cipher, Some(&salt), &ikm)
+        .expect("HKDF Extract should work");
+    let raw_prk = prk.as_bytes().expect("key should have bytes");
+    assert_eq!(raw_prk, expected);
+}
+
+#[test]
+fn extract_sha256() {
+    const EXPECTED: &[u8] = &[
+        0xa5, 0x68, 0x02, 0x5a, 0x95, 0xc9, 0x7f, 0x55, 0x38, 0xbc, 0xf7, 0x97, 0xcc, 0x0f, 0xd5,
+        0xf6, 0xa8, 0x8d, 0x15, 0xbc, 0x0e, 0x85, 0x74, 0x70, 0x3c, 0xa3, 0x65, 0xbd, 0x76, 0xcf,
+        0x9f, 0xd3,
+    ];
+    extract(TLS_AES_128_GCM_SHA256, EXPECTED);
+    extract(TLS_CHACHA20_POLY1305_SHA256, EXPECTED);
+}
+
+#[test]
+fn extract_sha384() {
+    extract(
+        TLS_AES_256_GCM_SHA384,
+        &[
+            0x01, 0x93, 0xc0, 0x07, 0x3f, 0x6a, 0x83, 0x0e, 0x2e, 0x4f, 0xb2, 0x58, 0xe4, 0x00,
+            0x08, 0x5c, 0x68, 0x9c, 0x37, 0x32, 0x00, 0x37, 0xff, 0xc3, 0x1c, 0x5b, 0x98, 0x0b,
+            0x02, 0x92, 0x3f, 0xfd, 0x73, 0x5a, 0x6f, 0x2a, 0x95, 0xa3, 0xee, 0xf6, 0xd6, 0x8e,
+            0x6f, 0x86, 0xea, 0x63, 0xf8, 0x33,
+        ],
+    );
+}
+
+fn derive_secret(cipher: Cipher, expected: &[u8]) {
+    fixture_init();
+
+    // Here we only use the salt as the PRK.
+    let (prk, _) = import_keys(cipher);
+    let secret = hkdf::expand_label(TLS_VERSION_1_3, cipher, &prk, &[], "master secret")
+        .expect("HKDF-Expand-Label should work");
+    let raw_secret = secret.as_bytes().expect("key should have bytes");
+    assert_eq!(raw_secret, expected);
+}
+
+#[test]
+fn derive_secret_sha256() {
+    const EXPECTED: &[u8] = &[
+        0xb7, 0x08, 0x00, 0xe3, 0x8e, 0x48, 0x68, 0x91, 0xb1, 0x0f, 0x5e, 0x6f, 0x22, 0x53, 0x6b,
+        0x84, 0x69, 0x75, 0xaa, 0xa3, 0x2a, 0xe7, 0xde, 0xaa, 0xc3, 0xd1, 0xb4, 0x05, 0x22, 0x5c,
+        0x68, 0xf5,
+    ];
+    derive_secret(TLS_AES_128_GCM_SHA256, EXPECTED);
+    derive_secret(TLS_CHACHA20_POLY1305_SHA256, EXPECTED);
+}
+
+#[test]
+fn derive_secret_sha384() {
+    derive_secret(
+        TLS_AES_256_GCM_SHA384,
+        &[
+            0x13, 0xd3, 0x36, 0x9f, 0x3c, 0x78, 0xa0, 0x32, 0x40, 0xee, 0x16, 0xe9, 0x11, 0x12,
+            0x66, 0xc7, 0x51, 0xad, 0xd8, 0x3c, 0xa1, 0xa3, 0x97, 0x74, 0xd7, 0x45, 0xff, 0xa7,
+            0x88, 0x9e, 0x52, 0x17, 0x2e, 0xaa, 0x3a, 0xd2, 0x35, 0xd8, 0xd5, 0x35, 0xfd, 0x65,
+            0x70, 0x9f, 0xa9, 0xf9, 0xfa, 0x23,
+        ],
+    );
+}
+
+fn expand_label(cipher: Cipher, expected: &[u8]) {
+    fixture_init();
+
+    let l = cipher_hash_len(cipher);
+    let (prk, _) = import_keys(cipher);
+    let secret = hkdf::expand_label(
+        TLS_VERSION_1_3,
+        cipher,
+        &prk,
+        &SESSION_HASH[0..l],
+        "master secret",
+    )
+    .expect("HKDF-Expand-Label should work");
+    let raw_secret = secret.as_bytes().expect("key should have bytes");
+    assert_eq!(raw_secret, expected);
+}
+
+#[test]
+fn expand_label_sha256() {
+    const EXPECTED: &[u8] = &[
+        0x3e, 0x4e, 0x6e, 0xd0, 0xbc, 0xc4, 0xf4, 0xff, 0xf0, 0xf5, 0x69, 0xd0, 0x6c, 0x1e, 0x0e,
+        0x10, 0x32, 0xaa, 0xd7, 0xa3, 0xef, 0xf6, 0xa8, 0x65, 0x8e, 0xbe, 0xee, 0xc7, 0x1f, 0x01,
+        0x6d, 0x3c,
+    ];
+    expand_label(TLS_AES_128_GCM_SHA256, EXPECTED);
+    expand_label(TLS_CHACHA20_POLY1305_SHA256, EXPECTED);
+}
+
+#[test]
+fn expand_label_sha384() {
+    expand_label(
+        TLS_AES_256_GCM_SHA384,
+        &[
+            0x41, 0xea, 0x77, 0x09, 0x8c, 0x90, 0x04, 0x10, 0xec, 0xbc, 0x37, 0xd8, 0x5b, 0x54,
+            0xcd, 0x7b, 0x08, 0x15, 0x13, 0x20, 0xed, 0x1e, 0x3f, 0x54, 0x74, 0xf7, 0x8b, 0x06,
+            0x38, 0x28, 0x06, 0x37, 0x75, 0x23, 0xa2, 0xb7, 0x34, 0xb1, 0x72, 0x2e, 0x59, 0x6d,
+            0x5a, 0x31, 0xf5, 0x53, 0xab, 0x99,
+        ],
+    );
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/tests/hp.rs
@@ -0,0 +1,55 @@
+#![deny(warnings)]
+
+use neqo_crypto::constants::*;
+use neqo_crypto::hkdf;
+use neqo_crypto::hp::HpKey;
+use test_fixture::fixture_init;
+
+fn make_hp(cipher: Cipher) -> HpKey {
+    let ikm = hkdf::import_key(TLS_VERSION_1_3, cipher, &[0; 16]).expect("import IKM");
+    let prk = hkdf::extract(TLS_VERSION_1_3, cipher, None, &ikm).expect("extract works");
+    HpKey::extract(TLS_VERSION_1_3, cipher, &prk, "hp").expect("extract label works")
+}
+
+#[test]
+fn aes128() {
+    fixture_init();
+    let mask = make_hp(TLS_AES_128_GCM_SHA256)
+        .mask(&[0; 16])
+        .expect("should produce a mask");
+    const EXPECTED: &[u8] = &[
+        0x04, 0x7b, 0xda, 0x65, 0xc3, 0x41, 0xcf, 0xbc, 0x5d, 0xe1, 0x75, 0x2b, 0x9d, 0x7d, 0xc3,
+        0x14,
+    ];
+    assert_eq!(mask, EXPECTED);
+}
+
+#[test]
+fn aes256() {
+    fixture_init();
+    let mask = make_hp(TLS_AES_256_GCM_SHA384)
+        .mask(&[0; 16])
+        .expect("should produce a mask");
+    const EXPECTED: &[u8] = &[
+        0xb5, 0xea, 0xa2, 0x1c, 0x25, 0x77, 0x48, 0x18, 0xbf, 0x25, 0xea, 0xfa, 0xbd, 0x8d, 0x80,
+        0x2b,
+    ];
+    assert_eq!(mask, EXPECTED);
+}
+
+#[cfg(feature = "chacha")]
+#[test]
+fn chacha20_ctr() {
+    fixture_init();
+    let mask = make_hp(TLS_CHACHA20_POLY1305_SHA256)
+        .mask(&[0; 16])
+        .expect("should produce a mask");
+    const EXPECTED: &[u8] = &[
+        0x34, 0x11, 0xb3, 0x53, 0x02, 0x0b, 0x16, 0xda, 0x0a, 0x85, 0x5a, 0x52, 0x0d, 0x06, 0x07,
+        0x1f, 0x4a, 0xb1, 0xaf, 0xf7, 0x83, 0xa8, 0xf0, 0x29, 0xc3, 0x19, 0xef, 0x57, 0x48, 0xe7,
+        0x8e, 0x3e, 0x11, 0x91, 0xe1, 0xd5, 0x92, 0x8f, 0x61, 0x6d, 0x3f, 0x3d, 0x76, 0xb5, 0x29,
+        0xf1, 0x62, 0x2f, 0x1e, 0xad, 0xdd, 0x23, 0x59, 0x45, 0xac, 0xd2, 0x19, 0x8a, 0xb4, 0x1f,
+        0x2f, 0x52, 0x46, 0x89,
+    ];
+    assert_eq!(mask, EXPECTED);
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/tests/init.rs
@@ -0,0 +1,37 @@
+#![deny(warnings)]
+
+// This uses external interfaces to neqo_crypto rather than being a module
+// inside of lib.rs. Because all other code uses the test_fixture module,
+// they will be calling into the public version of init_db().  Calling into
+// the version exposed to an inner module in lib.rs would result in calling
+// a different version of init_db.  That causes explosions as they get
+// different versions of the Once instance they use and they initialize NSS
+// twice, probably likely in parallel.  That doesn't work out well.
+use neqo_crypto::*;
+
+// Pull in the NSS internals so that we can ask NSS if it thinks that
+// it is properly initialized.
+mod nss {
+    #![allow(clippy::redundant_static_lifetimes, dead_code, non_upper_case_globals)]
+    include!(concat!(env!("OUT_DIR"), "/nss_init.rs"));
+}
+
+#[cfg(nss_nodb)]
+#[test]
+fn init_nodb() {
+    init();
+    assert_initialized();
+    unsafe {
+        assert!(nss::NSS_IsInitialized() != 0);
+    }
+}
+
+#[cfg(not(nss_nodb))]
+#[test]
+fn init_withdb() {
+    init_db(::test_fixture::NSS_DB_PATH);
+    assert_initialized();
+    unsafe {
+        assert!(nss::NSS_IsInitialized() != 0);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-crypto/tests/selfencrypt.rs
@@ -0,0 +1,90 @@
+#![deny(warnings)]
+
+use neqo_crypto::constants::*;
+use neqo_crypto::{init, selfencrypt::SelfEncrypt, Error};
+
+#[test]
+fn se_create() {
+    init();
+    SelfEncrypt::new(TLS_VERSION_1_3, TLS_AES_128_GCM_SHA256).expect("constructor works");
+}
+
+const PLAINTEXT: &[u8] = b"PLAINTEXT";
+const AAD: &[u8] = b"AAD";
+
+fn sealed() -> (SelfEncrypt, Vec<u8>) {
+    init();
+    let se = SelfEncrypt::new(TLS_VERSION_1_3, TLS_AES_128_GCM_SHA256).unwrap();
+    let sealed = se.seal(AAD, PLAINTEXT).expect("sealing works");
+    (se, sealed)
+}
+
+#[test]
+fn seal_open() {
+    let (se, sealed) = sealed();
+    let opened = se.open(AAD, &sealed).expect("opening works");
+    assert_eq!(&opened[..], PLAINTEXT);
+}
+
+#[test]
+fn seal_rotate_open() {
+    let (mut se, sealed) = sealed();
+    se.rotate().expect("rotate should be infallible");
+    let opened = se.open(AAD, &sealed).expect("opening works");
+    assert_eq!(&opened[..], PLAINTEXT);
+}
+
+#[test]
+fn seal_rotate_twice_open() {
+    let (mut se, sealed) = sealed();
+    se.rotate().expect("rotate should be infallible");
+    se.rotate().expect("rotate should be infallible");
+    let res = se.open(AAD, &sealed);
+    assert_eq!(res.unwrap_err(), Error::SelfEncryptFailure);
+}
+
+#[test]
+fn damage_version() {
+    let (se, mut sealed) = sealed();
+    sealed[0] ^= 0x80;
+    let res = se.open(AAD, &sealed);
+    assert_eq!(res.unwrap_err(), Error::SelfEncryptFailure);
+}
+
+fn assert_bad_data<T>(res: Result<T, Error>) {
+    if let Err(Error::NssError { name, .. }) = res {
+        assert_eq!(name, "SEC_ERROR_BAD_DATA");
+    }
+}
+
+#[test]
+fn damage_salt() {
+    let (se, mut sealed) = sealed();
+    sealed[4] ^= 0x10;
+    let res = se.open(AAD, &sealed);
+    assert_bad_data(res);
+}
+
+#[test]
+fn damage_ciphertext() {
+    let (se, mut sealed) = sealed();
+    sealed[20] ^= 0x2f;
+    let res = se.open(AAD, &sealed);
+    assert_bad_data(res);
+}
+
+#[test]
+fn damage_auth_tag() {
+    let (se, mut sealed) = sealed();
+    let idx = sealed.len() - 1;
+    sealed[idx] ^= 0x3;
+    let res = se.open(AAD, &sealed);
+    assert_bad_data(res);
+}
+
+#[test]
+fn truncate() {
+    let (se, sealed) = sealed();
+    let res = se.open(AAD, &sealed[0..(sealed.len() - 1)]);
+    assert_bad_data(res);
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-http3/.cargo-checksum.json
@@ -0,0 +1,1 @@
+{"files":{"Cargo.toml":"cf65b9eed3be7ef1289035b21a0c73a1e128131e5a64dd4cf7dcf13896ae39ac","src/client_events.rs":"569759b51e8eb5eb3fec2483da7f962f80216a9cd4bb7ef6e47d542eb9c189ec","src/connection.rs":"4b79a8e71e143c117b735b0ddb1948a19ed7234d39566594384ad665658b430b","src/connection_client.rs":"f9f9c232bcd8203936ae5df86e44239f84e6417c7f08cf2549fbc9874d050e5d","src/connection_server.rs":"e5c37953702a7665035dc1aa9dc3bf160bf4bb23375f10841902fa61ccbb1994","src/control_stream_local.rs":"8b685d5236ea59914ca91f6bd9613586ede581ca400e54e8f429f9e51580774b","src/control_stream_remote.rs":"94ba2cdeeb1e76e7bab7ed19ef02b9516fb4c13f496a4c8066f92230c62cf083","src/hframe.rs":"15dce78028666c12dbc99e252f3db8e87659da9260fc7bf0d2d182cb9e6d3081","src/lib.rs":"b1e7c07f7aff8db05fee153608d232e04c01480f991bbdd20374d9b0f8a930c7","src/server_events.rs":"f35c0f51d94ddc4ddfa2cc947f3ad51794766d2531bc009bfdcaca0d570ab691","src/stream_type_reader.rs":"e96f55f9e1df4e894d4d93250a79a4a46db1669d8c17353b94525db0a75c774e","src/transaction_client.rs":"aff0d8adf4b0c26ac84569b1beefea62f4ce87f394ba749d58548272ef5fd260","src/transaction_server.rs":"5962664c463ac4ae9e23a248fee45e5a9e7848a85aeee1fe81a9315e3c485a7d","tests/httpconn.rs":"e6655b424cc0f7116880fbedf3b7b0a1e602e103b3b91ae802fa5b3c11db4160"},"package":null}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-http3/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "neqo-http3"
+version = "0.1.4"
+authors = ["draganadamjanovic"]
+edition = "2018"
+
+[dependencies]
+neqo-common = { path = "./../neqo-common" }
+neqo-crypto = { path = "./../neqo-crypto" }
+neqo-transport = { path = "./../neqo-transport" }
+neqo-qpack = { path = "./../neqo-qpack" }
+num-traits = "0.2"
+log = "0.4.0"
+smallvec = "0.6.6"
+
+[dev-dependencies]
+test-fixture = { path = "../test-fixture" }
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/client_events.rs
@@ -0,0 +1,123 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::connection::{Http3Events, Http3State};
+use neqo_transport::{AppError, StreamType};
+
+use smallvec::SmallVec;
+use std::cell::RefCell;
+use std::collections::BTreeSet;
+use std::rc::Rc;
+
+#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone)]
+pub enum Http3ClientEvent {
+    /// Space available in the buffer for an application write to succeed.
+    HeaderReady { stream_id: u64 },
+    /// A stream can accept new data.
+    DataWritable { stream_id: u64 },
+    /// New bytes available for reading.
+    DataReadable { stream_id: u64 },
+    /// Peer reset the stream.
+    Reset { stream_id: u64, error: AppError },
+    /// Peer has send STOP_SENDING with error code EarlyResponse, other error will post a reset event.
+    StopSending { stream_id: u64, error: AppError },
+    ///A new push stream
+    NewPushStream { stream_id: u64 },
+    /// New stream can be created
+    RequestsCreatable,
+    /// Cert authentication needed
+    AuthenticationNeeded,
+    /// Client has received a GOAWAY frame
+    GoawayReceived,
+    /// Connection state change.
+    StateChange(Http3State),
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct Http3ClientEvents {
+    events: Rc<RefCell<BTreeSet<Http3ClientEvent>>>,
+}
+
+impl Http3ClientEvents {
+    pub fn header_ready(&self, stream_id: u64) {
+        self.insert(Http3ClientEvent::HeaderReady { stream_id });
+    }
+
+    pub fn data_readable(&self, stream_id: u64) {
+        self.insert(Http3ClientEvent::DataReadable { stream_id });
+    }
+
+    pub fn stop_sending(&self, stream_id: u64, error: AppError) {
+        self.insert(Http3ClientEvent::StopSending { stream_id, error });
+    }
+
+    // TODO: implement push.
+    // pub fn new_push_stream(&self, stream_id: u64) {
+    //     self.insert(Http3ClientEvent::NewPushStream { stream_id });
+    // }
+
+    pub fn authentication_needed(&self) {
+        self.insert(Http3ClientEvent::AuthenticationNeeded);
+    }
+
+    pub fn goaway_received(&self) {
+        self.insert(Http3ClientEvent::GoawayReceived);
+    }
+
+    pub fn events(&self) -> impl Iterator<Item = Http3ClientEvent> {
+        self.events.replace(BTreeSet::new()).into_iter()
+    }
+
+    fn insert(&self, event: Http3ClientEvent) {
+        self.events.borrow_mut().insert(event);
+    }
+
+    pub fn remove(&self, event: &Http3ClientEvent) -> bool {
+        self.events.borrow_mut().remove(event)
+    }
+}
+
+impl Http3Events for Http3ClientEvents {
+    fn data_writable(&self, stream_id: u64) {
+        self.insert(Http3ClientEvent::DataWritable { stream_id });
+    }
+
+    fn reset(&self, stream_id: u64, error: AppError) {
+        self.insert(Http3ClientEvent::Reset { stream_id, error });
+    }
+
+    fn new_requests_creatable(&self, stream_type: StreamType) {
+        if stream_type == StreamType::BiDi {
+            self.insert(Http3ClientEvent::RequestsCreatable);
+        }
+    }
+
+    fn connection_state_change(&self, state: Http3State) {
+        self.insert(Http3ClientEvent::StateChange(state));
+    }
+
+    fn remove_events_for_stream_id(&self, remove_stream_id: u64) {
+        let events_to_remove = self
+            .events
+            .borrow()
+            .iter()
+            .filter(|evt| match evt {
+                Http3ClientEvent::HeaderReady { stream_id }
+                | Http3ClientEvent::DataWritable { stream_id }
+                | Http3ClientEvent::DataReadable { stream_id }
+                | Http3ClientEvent::NewPushStream { stream_id }
+                | Http3ClientEvent::Reset { stream_id, .. }
+                | Http3ClientEvent::StopSending { stream_id, .. } => *stream_id == remove_stream_id,
+                _ => false,
+            })
+            .cloned()
+            .collect::<SmallVec<[_; 8]>>();
+
+        for evt in events_to_remove {
+            self.remove(&evt);
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/connection.rs
@@ -0,0 +1,855 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::client_events::{Http3ClientEvent, Http3ClientEvents};
+use crate::control_stream_local::{ControlStreamLocal, HTTP3_UNI_STREAM_TYPE_CONTROL};
+use crate::control_stream_remote::ControlStreamRemote;
+use crate::hframe::{HFrame, HSettingType};
+use crate::server_events::Http3ServerEvents;
+use crate::stream_type_reader::NewStreamTypeReader;
+use crate::transaction_client::TransactionClient;
+use crate::transaction_server::TransactionServer;
+use neqo_common::{qdebug, qerror, qinfo, qtrace, qwarn, Datagram};
+use neqo_crypto::AntiReplay;
+use neqo_qpack::decoder::{QPackDecoder, QPACK_UNI_STREAM_TYPE_DECODER};
+use neqo_qpack::encoder::{QPackEncoder, QPACK_UNI_STREAM_TYPE_ENCODER};
+use neqo_transport::{
+    AppError, CloseError, Connection, ConnectionEvent, ConnectionIdManager, Output, Role, State,
+    StreamType,
+};
+use std::cell::RefCell;
+use std::collections::{BTreeSet, HashMap};
+use std::fmt::Debug;
+use std::mem;
+use std::net::SocketAddr;
+use std::rc::Rc;
+use std::time::Instant;
+
+use crate::{Error, Res};
+
+const HTTP3_UNI_STREAM_TYPE_PUSH: u64 = 0x1;
+
+const MAX_HEADER_LIST_SIZE_DEFAULT: u64 = u64::max_value();
+
+pub trait Http3Events: Default + Debug {
+    fn data_writable(&self, stream_id: u64);
+    fn reset(&self, stream_id: u64, error: AppError);
+    fn new_requests_creatable(&self, stream_type: StreamType);
+    fn connection_state_change(&self, state: Http3State);
+    fn remove_events_for_stream_id(&self, remove_stream_id: u64);
+}
+
+pub trait Http3Transaction: Debug {
+    fn send(&mut self, conn: &mut Connection, encoder: &mut QPackEncoder) -> Res<()>;
+    fn receive(&mut self, conn: &mut Connection, decoder: &mut QPackDecoder) -> Res<()>;
+    fn has_data_to_send(&self) -> bool;
+    fn is_state_sending_data(&self) -> bool;
+    fn reset_receiving_side(&mut self);
+    fn stop_sending(&mut self);
+    fn done(&self) -> bool;
+    fn close_send(&mut self, conn: &mut Connection) -> Res<()>;
+}
+
+pub trait Http3Handler<E: Http3Events, T: Http3Transaction> {
+    fn new() -> Self;
+    fn handle_new_bidi_stream(
+        &mut self,
+        transactions: &mut HashMap<u64, T>,
+        stream_id: u64,
+    ) -> Res<()>;
+    fn handle_stream_stop_sending(
+        &mut self,
+        transactions: &mut HashMap<u64, T>,
+        events: &mut E,
+        conn: &mut Connection,
+        stop_stream_id: u64,
+        app_err: AppError,
+    ) -> Res<()>;
+    fn handle_new_push_stream(&mut self) -> Res<()>;
+    fn handle_goaway(
+        &mut self,
+        transactions: &mut HashMap<u64, T>,
+        events: &mut E,
+        state: &mut Http3State,
+        goaway_stream_id: u64,
+    ) -> Res<()>;
+    fn handle_max_push_id(&mut self, stream_id: u64) -> Res<()>;
+    fn handle_authentication_needed(&self, events: &mut E) -> Res<()>;
+}
+
+#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Clone)]
+pub enum Http3State {
+    Initializing,
+    Connected,
+    GoingAway,
+    Closing(CloseError),
+    Closed(CloseError),
+}
+
+pub struct Http3Connection<E: Http3Events, T: Http3Transaction, H: Http3Handler<E, T>> {
+    pub state: Http3State,
+    pub conn: Connection,
+    max_header_list_size: u64,
+    control_stream_local: ControlStreamLocal,
+    control_stream_remote: ControlStreamRemote,
+    new_streams: HashMap<u64, NewStreamTypeReader>,
+    pub qpack_encoder: QPackEncoder,
+    pub qpack_decoder: QPackDecoder,
+    settings_received: bool,
+    streams_have_data_to_send: BTreeSet<u64>,
+    pub events: E,
+    pub transactions: HashMap<u64, T>,
+    handler: H,
+}
+
+impl<E: Http3Events + Default, T: Http3Transaction, H: Http3Handler<E, T>> ::std::fmt::Display
+    for Http3Connection<E, T, H>
+{
+    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+        write!(f, "Http3 connection {:?}", self.role())
+    }
+}
+
+impl<E: Http3Events + Default, T: Http3Transaction, H: Http3Handler<E, T>>
+    Http3Connection<E, T, H>
+{
+    pub fn new_client(
+        server_name: &str,
+        protocols: &[impl AsRef<str>],
+        cid_manager: Rc<RefCell<dyn ConnectionIdManager>>,
+        local_addr: SocketAddr,
+        remote_addr: SocketAddr,
+        max_table_size: u32,
+        max_blocked_streams: u16,
+    ) -> Res<Self> {
+        if max_table_size > (1 << 30) - 1 {
+            panic!("Wrong max_table_size");
+        }
+        let conn =
+            Connection::new_client(server_name, protocols, cid_manager, local_addr, remote_addr)?;
+        Http3Connection::new_client_with_conn(conn, max_table_size, max_blocked_streams)
+    }
+
+    pub fn new_client_with_conn(
+        c: Connection,
+        max_table_size: u32,
+        max_blocked_streams: u16,
+    ) -> Res<Self> {
+        if max_table_size > (1 << 30) - 1 {
+            panic!("Wrong max_table_size");
+        }
+        Ok(Http3Connection {
+            state: Http3State::Initializing,
+            conn: c,
+            max_header_list_size: MAX_HEADER_LIST_SIZE_DEFAULT,
+            control_stream_local: ControlStreamLocal::default(),
+            control_stream_remote: ControlStreamRemote::new(),
+            new_streams: HashMap::new(),
+            qpack_encoder: QPackEncoder::new(true),
+            qpack_decoder: QPackDecoder::new(max_table_size, max_blocked_streams),
+            settings_received: false,
+            streams_have_data_to_send: BTreeSet::new(),
+            events: E::default(),
+            transactions: HashMap::new(),
+            handler: H::new(),
+        })
+    }
+
+    pub fn new_server(
+        certs: &[impl AsRef<str>],
+        protocols: &[impl AsRef<str>],
+        anti_replay: &AntiReplay,
+        cid_manager: Rc<RefCell<dyn ConnectionIdManager>>,
+        max_table_size: u32,
+        max_blocked_streams: u16,
+    ) -> Res<Self> {
+        if max_table_size > (1 << 30) - 1 {
+            panic!("Wrong max_table_size");
+        }
+        Ok(Http3Connection {
+            state: Http3State::Initializing,
+            conn: Connection::new_server(certs, protocols, anti_replay, cid_manager)?,
+            max_header_list_size: MAX_HEADER_LIST_SIZE_DEFAULT,
+            control_stream_local: ControlStreamLocal::default(),
+            control_stream_remote: ControlStreamRemote::new(),
+            new_streams: HashMap::new(),
+            qpack_encoder: QPackEncoder::new(true),
+            qpack_decoder: QPackDecoder::new(max_table_size, max_blocked_streams),
+            settings_received: false,
+            streams_have_data_to_send: BTreeSet::new(),
+            events: E::default(),
+            transactions: HashMap::new(),
+            handler: H::new(),
+        })
+    }
+
+    fn initialize_http3_connection(&mut self) -> Res<()> {
+        qtrace!([self] "Initialize the http3 connection.");
+        self.control_stream_local.create(&mut self.conn)?;
+        self.send_settings();
+        self.create_qpack_streams()?;
+        Ok(())
+    }
+
+    fn send_settings(&mut self) {
+        qtrace!([self] "Send settings.");
+        self.control_stream_local.queue_frame(HFrame::Settings {
+            settings: vec![
+                (
+                    HSettingType::MaxTableSize,
+                    self.qpack_decoder.get_max_table_size().into(),
+                ),
+                (
+                    HSettingType::BlockedStreams,
+                    self.qpack_decoder.get_blocked_streams().into(),
+                ),
+            ],
+        });
+    }
+
+    fn create_qpack_streams(&mut self) -> Res<()> {
+        qtrace!([self] "create_qpack_streams.");
+        self.qpack_encoder
+            .add_send_stream(self.conn.stream_create(StreamType::UniDi)?);
+        self.qpack_decoder
+            .add_send_stream(self.conn.stream_create(StreamType::UniDi)?);
+        Ok(())
+    }
+
+    // This function takes the provided result and check for an error.
+    // An error results in closing the connection.
+    fn check_result<ERR>(&mut self, now: Instant, res: Res<ERR>) -> bool {
+        match &res {
+            Err(e) => {
+                qinfo!([self] "Connection error: {}.", e);
+