diff --git a/Cargo.lock b/Cargo.lock index c5ee93f04..103d2e8ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,266 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "actix-codec" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78d1833b3838dbe990df0f1f87baf640cf6146e898166afe401839d1b001e570" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project 0.4.27", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-connect" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177837a10863f15ba8d3ae3ec12fac1099099529ed20083a27fdfe247381d0dc" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "derive_more", + "either", + "futures-util", + "http", + "log", + "trust-dns-proto", + "trust-dns-resolver", +] + +[[package]] +name = "actix-http" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "452299e87817ae5673910e53c243484ca38be3828db819b6011736fc6982e874" +dependencies = [ + "actix-codec", + "actix-connect", + "actix-rt", + "actix-service", + "actix-threadpool", + "actix-utils", + "base64 0.13.0", + "bitflags", + "brotli2", + "bytes", + "cookie", + "copyless", + "derive_more", + "either", + "encoding_rs", + "flate2", + "futures-channel", + "futures-core", + "futures-util", + "fxhash", + "h2", + "http", + "httparse", + "indexmap", + "itoa", + "language-tags", + "lazy_static", + "log", + "mime", + "percent-encoding", + "pin-project 1.0.2", + "rand 0.7.3", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "sha-1", + "slab", + "time 0.2.23", +] + +[[package]] +name = "actix-macros" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a60f9ba7c4e6df97f3aacb14bb5c0cd7d98a49dcbaed0d7f292912ad9a6a3ed2" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "actix-router" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd1f7dbda1645bf7da33554db60891755f6c01c1b2169e2f4c492098d30c235" +dependencies = [ + "bytestring", + "http", + "log", + "regex", + "serde", +] + +[[package]] +name = "actix-rt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143fcc2912e0d1de2bcf4e2f720d2a60c28652ab4179685a1ee159e0fb3db227" +dependencies = [ + "actix-macros", + "actix-threadpool", + "copyless", + "futures-channel", + "futures-util", + "smallvec", + "tokio", +] + +[[package]] +name = "actix-server" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45407e6e672ca24784baa667c5d32ef109ccdd8d5e0b5ebb9ef8a67f4dfb708e" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "futures-channel", + "futures-util", + "log", + "mio", + "mio-uds", + "num_cpus", + "slab", + "socket2", +] + +[[package]] +name = "actix-service" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0052435d581b5be835d11f4eb3bce417c8af18d87ddf8ace99f8e67e595882bb" +dependencies = [ + "futures-util", + "pin-project 0.4.27", +] + +[[package]] +name = "actix-testing" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47239ca38799ab74ee6a8a94d1ce857014b2ac36f242f70f3f75a66f691e791c" +dependencies = [ + "actix-macros", + "actix-rt", + "actix-server", + "actix-service", + "log", + "socket2", +] + +[[package]] +name = "actix-threadpool" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d209f04d002854b9afd3743032a27b066158817965bf5d036824d19ac2cc0e30" +dependencies = [ + "derive_more", + "futures-channel", + "lazy_static", + "log", + "num_cpus", + "parking_lot", + "threadpool", +] + +[[package]] +name = "actix-tls" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24789b7d7361cf5503a504ebe1c10806896f61e96eca9a7350e23001aca715fb" +dependencies = [ + "actix-codec", + "actix-service", + "actix-utils", + "futures-util", +] + +[[package]] +name = "actix-utils" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9022dec56632d1d7979e59af14f0597a28a830a9c1c7fec8b2327eb9f16b5a" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "bitflags", + "bytes", + "either", + "futures-channel", + "futures-sink", + "futures-util", + "log", + "pin-project 0.4.27", + "slab", +] + +[[package]] +name = "actix-web" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a89a7b133e734f6d1e555502d450408ae04105826aef7e3605019747d3ac732" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-testing", + "actix-threadpool", + "actix-tls", + "actix-utils", + "actix-web-codegen", + "awc", + "bytes", + "derive_more", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "fxhash", + "log", + "mime", + "pin-project 1.0.2", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "socket2", + "time 0.2.23", + "tinyvec", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad26f77093333e0e7c6ffe54ebe3582d908a104e448723eec6d43d08b07143fb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "addr2line" version = "0.14.0" @@ -30,7 +291,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -58,6 +319,17 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "async-trait" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atty" version = "0.2.14" @@ -66,7 +338,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -81,6 +353,30 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "awc" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9056f5e27b0d56bedd82f78eceaba0bcddcbbcbbefae3cd0a53994b28c96ff5" +dependencies = [ + "actix-codec", + "actix-http", + "actix-rt", + "actix-service", + "base64 0.13.0", + "bytes", + "cfg-if 1.0.0", + "derive_more", + "futures-core", + "log", + "mime", + "percent-encoding", + "rand 0.7.3", + "serde", + "serde_json", + "serde_urlencoded", +] + [[package]] name = "backtrace" version = "0.3.55" @@ -95,12 +391,24 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base-x" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" + [[package]] name = "base64" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + [[package]] name = "bit-set" version = "0.5.2" @@ -133,6 +441,35 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "brotli2" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e" +dependencies = [ + "brotli-sys", + "libc", +] + [[package]] name = "bstr" version = "0.2.14" @@ -157,6 +494,21 @@ version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "bytestring" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7c05fa5172da78a62d9949d662d2ac89d4cc7355d7b49adee5163f1fb3f363" +dependencies = [ + "bytes", +] + [[package]] name = "cc" version = "1.0.65" @@ -185,8 +537,8 @@ dependencies = [ "num-integer", "num-traits", "serde", - "time", - "winapi", + "time 0.1.44", + "winapi 0.3.9", ] [[package]] @@ -213,12 +565,59 @@ dependencies = [ "bitflags", ] +[[package]] +name = "cloudabi" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467" +dependencies = [ + "bitflags", +] + +[[package]] +name = "const_fn" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab" + [[package]] name = "constant_time_eq" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "cookie" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784ad0fbab4f3e9cef09f20e0aea6000ae08d2cb98ac4c0abc53df18803d702f" +dependencies = [ + "percent-encoding", + "time 0.2.23", + "version_check", +] + +[[package]] +name = "copyless" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" + +[[package]] +name = "cpuid-bool" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" + +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "crossbeam-utils" version = "0.7.2" @@ -252,12 +651,32 @@ dependencies = [ "memchr", ] +[[package]] +name = "derive_more" +version = "0.99.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "difference" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "dirs" version = "1.0.5" @@ -266,21 +685,54 @@ checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" dependencies = [ "libc", "redox_users", - "winapi", + "winapi 0.3.9", ] +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + [[package]] name = "encode_unicode" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "encoding_rs" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801bbab217d7f79c0062f4f7205b5d4427c6d1a7bd7aafdd1475f7c59d62b283" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "enum-as-inner" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "failure" version = "0.1.8" @@ -303,6 +755,18 @@ dependencies = [ "synstructure", ] +[[package]] +name = "flate2" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129" +dependencies = [ + "cfg-if 1.0.0", + "crc32fast", + "libc", + "miniz_oxide", +] + [[package]] name = "float-cmp" version = "0.8.0" @@ -318,12 +782,152 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00" +dependencies = [ + "matches", + "percent-encoding", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748" + +[[package]] +name = "futures-executor" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4caa2b2b68b880003057c1dd49f1ed937e38f22fcf6c212188a121f08cf40a65" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb" + +[[package]] +name = "futures-macro" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d" + +[[package]] +name = "futures-task" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d" +dependencies = [ + "once_cell", +] + +[[package]] +name = "futures-util" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project 1.0.2", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.1.15" @@ -341,6 +945,41 @@ version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" +[[package]] +name = "h2" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", + "tracing-futures", +] + +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.1.17" @@ -350,6 +989,85 @@ dependencies = [ "libc", ] +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi 0.3.9", +] + +[[package]] +name = "http" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" + +[[package]] +name = "idna" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2" +dependencies = [ + "autocfg 1.0.1", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "ipconfig" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" +dependencies = [ + "socket2", + "widestring", + "winapi 0.3.9", + "winreg", +] + [[package]] name = "itoa" version = "0.4.6" @@ -365,6 +1083,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + [[package]] name = "kv" version = "0.10.0" @@ -378,6 +1106,12 @@ dependencies = [ "toml", ] +[[package]] +name = "language-tags" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" + [[package]] name = "lazy_static" version = "1.4.0" @@ -390,6 +1124,12 @@ version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" +[[package]] +name = "linked-hash-map" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" + [[package]] name = "lmdb-rkv" version = "0.12.3" @@ -413,6 +1153,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "lock_api" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.11" @@ -422,12 +1171,39 @@ dependencies = [ "cfg-if 0.1.10", ] +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + [[package]] name = "memchr" version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + [[package]] name = "miniz_oxide" version = "0.4.3" @@ -438,6 +1214,59 @@ dependencies = [ "autocfg 1.0.1", ] +[[package]] +name = "mio" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio-uds" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +dependencies = [ + "iovec", + "libc", + "mio", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "net2" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ebc3ec692ed7c9a255596c67808dee269f64655d8baf7b4f0638e51ba1d6853" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -463,6 +1292,16 @@ dependencies = [ "autocfg 1.0.1", ] +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "object" version = "0.22.0" @@ -475,6 +1314,102 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" +dependencies = [ + "cfg-if 0.1.10", + "cloudabi 0.1.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi 0.3.9", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15" +dependencies = [ + "pin-project-internal 0.4.27", +] + +[[package]] +name = "pin-project" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7" +dependencies = [ + "pin-project-internal 1.0.2", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b" + +[[package]] +name = "pin-project-lite" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.19" @@ -530,6 +1465,18 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro-nested" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" + [[package]] name = "proc-macro2" version = "1.0.24" @@ -584,7 +1531,7 @@ dependencies = [ "libc", "rand_core 0.3.1", "rdrand", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -603,7 +1550,7 @@ dependencies = [ "rand_os", "rand_pcg", "rand_xorshift", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -698,7 +1645,7 @@ checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" dependencies = [ "libc", "rand_core 0.4.2", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -707,12 +1654,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" dependencies = [ - "cloudabi", + "cloudabi 0.0.3", "fuchsia-cprng", "libc", "rand_core 0.4.2", "rdrand", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -793,7 +1740,17 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "winapi", + "winapi 0.3.9", +] + +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error", ] [[package]] @@ -808,7 +1765,7 @@ dependencies = [ "spin", "untrusted", "web-sys", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -838,7 +1795,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19" dependencies = [ - "base64", + "base64 0.12.3", "blake2b_simd", "constant_time_eq", "crossbeam-utils", @@ -850,6 +1807,15 @@ version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + [[package]] name = "rusty-fork" version = "0.2.2" @@ -868,6 +1834,27 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" version = "1.0.117" @@ -899,12 +1886,134 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce3cdf1b5e620a498ee6f2a171885ac7e22f0e12089ec4b3d22b84921792507c" +dependencies = [ + "block-buffer", + "cfg-if 1.0.0", + "cpuid-bool", + "digest", + "opaque-debug", +] + +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" + +[[package]] +name = "signal-hook-registry" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "smallvec" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85" + +[[package]] +name = "socket2" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "winapi 0.3.9", +] + [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "standback" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf906c8b8fc3f6ecd1046e01da1d8ddec83e48c8b08b84dcc02b585a6bedf5a8" +dependencies = [ + "version_check", +] + +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2", + "quote", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + [[package]] name = "strsim" version = "0.8.0" @@ -925,6 +2034,13 @@ dependencies = [ [[package]] name = "sync-server" version = "0.1.0" +dependencies = [ + "actix-rt", + "actix-web", + "failure", + "futures", + "taskchampion", +] [[package]] name = "synstructure" @@ -987,7 +2103,7 @@ dependencies = [ "rand 0.7.3", "redox_syscall", "remove_dir_all", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -998,7 +2114,7 @@ checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" dependencies = [ "byteorder", "dirs", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1039,6 +2155,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + [[package]] name = "time" version = "0.1.44" @@ -1047,7 +2172,94 @@ checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", + "winapi 0.3.9", +] + +[[package]] +name = "time" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcdaeea317915d59b2b4cd3b5efcd156c309108664277793f5351700c02ce98b" +dependencies = [ + "const_fn", + "libc", + "standback", + "stdweb", + "time-macros", + "version_check", + "winapi 0.3.9", +] + +[[package]] +name = "time-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "standback", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d7ad61edd59bfcc7e80dababf0f4aed2e6d5e0ba1659356ae889752dfc12ff" +dependencies = [ + "bytes", + "futures-core", + "iovec", + "lazy_static", + "libc", + "memchr", + "mio", + "mio-uds", + "pin-project-lite 0.1.11", + "signal-hook-registry", + "slab", + "winapi 0.3.9", +] + +[[package]] +name = "tokio-util" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite 0.1.11", + "tokio", ] [[package]] @@ -1059,12 +2271,113 @@ dependencies = [ "serde", ] +[[package]] +name = "tracing" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3" +dependencies = [ + "cfg-if 1.0.0", + "log", + "pin-project-lite 0.2.0", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tracing-futures" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab7bb6f14721aa00656086e9335d363c5c8747bae02ebe32ea2c7dece5689b4c" +dependencies = [ + "pin-project 0.4.27", + "tracing", +] + [[package]] name = "treeline" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" +[[package]] +name = "trust-dns-proto" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53861fcb288a166aae4c508ae558ed18b53838db728d4d310aad08270a7d4c2b" +dependencies = [ + "async-trait", + "backtrace", + "enum-as-inner", + "futures", + "idna", + "lazy_static", + "log", + "rand 0.7.3", + "smallvec", + "thiserror", + "tokio", + "url", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6759e8efc40465547b0dfce9500d733c65f969a4cbbfbe3ccf68daaa46ef179e" +dependencies = [ + "backtrace", + "cfg-if 0.1.10", + "futures", + "ipconfig", + "lazy_static", + "log", + "lru-cache", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "trust-dns-proto", +] + +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" + [[package]] name = "unicode-width" version = "0.1.8" @@ -1083,6 +2396,18 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "url" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + [[package]] name = "uuid" version = "0.8.1" @@ -1099,6 +2424,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -1184,6 +2515,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "widestring" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + [[package]] name = "winapi" version = "0.3.9" @@ -1194,6 +2537,12 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -1205,3 +2554,22 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winreg" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 4555c20c6..91a180ab5 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -7,5 +7,7 @@ - [Replica Storage](./storage.md) - [Task Database](./taskdb.md) - [Tasks](./tasks.md) -- [Synchronization](./sync.md) +- [Synchronization and the Sync Server](./sync.md) + - [Synchronization Model](./sync-model.md) + * [Server-Replica Protocol](./sync-protocol.md) - [Planned Functionality](./plans.md) diff --git a/docs/src/sync-model.md b/docs/src/sync-model.md new file mode 100644 index 000000000..691312efa --- /dev/null +++ b/docs/src/sync-model.md @@ -0,0 +1,128 @@ +# Synchronization Model + +The [task database](./taskdb.md) also implements synchronization. +Synchronization occurs between disconnected replicas, mediated by a server. +The replicas never communicate directly with one another. +The server does not have access to the task data; it sees only opaque blobs of data with a small amount of metadata. + +The synchronization process is a critical part of the task database's functionality, and it cannot function efficiently without occasional synchronization operations + +## Operational Transforms + +Synchronization is based on [operational transformation](https://en.wikipedia.org/wiki/Operational_transformation). +This section will assume some familiarity with the concept. + +## State and Operations + +At a given time, the set of tasks in a replica's storage is the essential "state" of that replica. +All modifications to that state occur via operations, as defined in [Replica Storage](./storage.md). +We can draw a network, or graph, with the nodes representing states and the edges representing operations. +For example: + +```text + o -- State: {abc-d123: 'get groceries', priority L} + | + | -- Operation: set abc-d123 priority to H + | + o -- State: {abc-d123: 'get groceries', priority H} +``` + +For those familiar with distributed version control systems, a state is analogous to a revision, while an operation is analogous to a commit. + +Fundamentally, synchronization involves all replicas agreeing on a single, linear sequence of operations and the state that those operations create. +Since the replicas are not connected, each may have additional operations that have been applied locally, but which have not yet been agreed on. +The synchronization process uses operational transformation to "linearize" those operations. +This process is analogous (vaguely) to rebasing a sequence of Git commits. + +### Versions + +Occasionally, database states are given a name (that takes the form of a UUID). +The system as a whole (all replicas) constructs a branch-free sequence of versions and the operations that separate each version from the next. +The version with the nil UUID is implicitly the empty database. + +The server stores the operations to change a state from a "parent" version to a "child" version, and provides that information as needed to replicas. +Replicas use this information to update their local task databases, and to generate new versions to send to the server. + +Replicas generate a new version to transmit local changes to the server. +The changes are represented as a sequence of operations with the state resulting from the final operation corresponding to the version. +In order to keep the versions in a single sequence, the server will only accept a proposed version from a replica if its parent version matches the latest version on the server. + +In the non-conflict case (such as with a single replica), then, a replica's synchronization process involves gathering up the operations it has accumulated since its last synchronization; bundling those operations into a version; and sending that version to the server. + +### Replica Invariant + +The replica's [storage](./storage.md) contains the current state in `tasks`, the as-yet un-synchronized operations in `operations`, and the last version at which synchronization occurred in `base_version`. + +The replica's un-synchronized operations are already reflected in its local `tasks`, so the following invariant holds: + +> Applying `operations` to the set of tasks at `base_version` gives a set of tasks identical +> to `tasks`. + +### Transformation + +When the latest version on the server contains operations that are not present in the replica, then the states have diverged. +For example: + +```text + o -- version N + w|\a + o o + x| \b + o o + y| \c + o o -- replica's local state + z| + o -- version N+1 +``` + +(diagram notation: `o` designates a state, lower-case letters designate operations, and versions are presented as if they were numbered sequentially) + +In this situation, the replica must "rebase" the local operations onto the latest version from the server and try again. +This process is performed using operational transformation (OT). +The result of this transformation is a sequence of operations based on the latest version, and a sequence of operations the replica can apply to its local task database to reach the same state +Continuing the example above, the resulting operations are shown with `'`: + +```text + o -- version N + w|\a + o o + x| \b + o o + y| \c + o o -- replica's intermediate local state + z| |w' + o-N+1 o + a'\ |x' + o o + b'\ |y' + o o + c'\|z' + o -- version N+2 +``` + +The replica applies w' through z' locally, and sends a' through c' to the server as the operations to generate version N+2. +Either path through this graph, a-b-c-w'-x'-y'-z' or a'-b'-c'-w-x-y-z, must generate *precisely* the same final state at version N+2. +Careful selection of the operations and the transformation function ensure this. + +See the comments in the source code for the details of how this transformation process is implemented. + +## Synchronization Process + +To perform a synchronization, the replica first requests the child version of `base_version` from the server (GetChildVersion). +It applies that version to its local `tasks`, rebases its local `operations` as described above, and updates `base_version`. +The replica repeats this process until the server indicates no additional child versions exist. +If there are no un-synchronized local operations, the process is complete. + +Otherwise, the replica creates a new version containing its local operations, giving its `base_version` as the parent version, and transmits that to the server (AddVersion). +In most cases, this will succeed, but if another replica has created a new version in the interim, then the new version will conflict with that other replica's new version and the server will respond with the new expected parent version. +In this case, the process repeats. +If the server indicates a conflict twice with the same expected base version, that is an indication that the replica has diverged (something serious has gone wrong). + +## Servers + +A replica depends on periodic synchronization for performant operation. +Without synchronization, its list of pending operations would grow indefinitely, and tasks could never be expired. +So all replicas, even "singleton" replicas which do not replicate task data with any other replica, must synchronize periodically. + +TaskChampion provides a `LocalServer` for this purpose. +It implements the `get_child_version` and `add_version` operations as described, storing data on-disk locally, all within the `task` binary. diff --git a/docs/src/sync-protocol.md b/docs/src/sync-protocol.md new file mode 100644 index 000000000..9a5caa247 --- /dev/null +++ b/docs/src/sync-protocol.md @@ -0,0 +1,92 @@ +# Server-Replica Protocol + +The server-replica protocol is defined abstractly in terms of request/response transactions from the replica to the server. +This is made concrete in an HTTP representation. + +The protocol builds on the model presented in the previous chapter, and in particular on the synchronization process. + +## Clients + +From the server's perspective, replicas are indistinguishable, so this protocol uses the term "client" to refer generically to all replicas replicating a single task history. + +## Server + +For each client, the server is responsible for storing the task history, in the form of a branch-free sequence of versions. + +For each client, it stores a set of versions as well as the latest version ID, defaulting to the nil UUID. +Each version has a version ID, a parent version ID, and a history segment (opaque data containing the operations for that version). +The server should maintain the following invariants: + +1. Given a client c, c.latestVersion is nil or exists in the set of versions. +1. Given versions v1 and v2 for a client, with v1.versionId != v2.versionId and v1.parentVersionId != nil, v1.parentVersionId != v2.parentVersionId. + In other words, versions do not branch. + +Note that versions form a linked list beginning with the version stored in he client. +This linked list need not continue back to a version with v.parentVersionId = nil. +It may end at any point when v.parentVersionId is not found in the set of Versions. +This observation allows the server to discard older versions. + +## Transactions + +### AddVersion + +The AddVersion transaction requests that the server add a new version to the client's task history. +The request contains the following; + + * parent version ID + * history segment + +The server determines whether the new version is acceptable, atomically with respect to other requests for the same client. +If it has no versions for the client, it accepts the version. +If it already has one or more versions for the client, then it accepts the version only if the given parent version ID matches its stored latest parent ID. + +If the version is accepted, the server generates a new version ID for it. +The version is added to the set of versions for the client, the client's latest version ID is set to the new version ID. +The new version ID is returned in the response to the client. + +If the version is not accepted, the server makes no changes, but responds to the client with a conflict indication containing the latest version ID. +The client may then "rebase" its operations and try again. +Note that if a client receives two conflict responses with the same parent version ID, it is an indication that the client's version history has diverged from that on the server. + +### GetChildVersion + +The GetChildVersion transaction is a read-only request for a version. +The request consists of a parent version ID. +The server searches its set of versions for a version with the given parent ID. +If found, it returns the version's + + * version ID, + * parent version ID (matching that in the request), and + * history segment. + +If not found, the server returns a negative response. + +## HTTP Representation + +The transactions above are realized for an HTTP server at `` using the HTTP requests and responses described here. +The `origin` *should* be an HTTPS endpoint on general principle, but nothing in the functonality or security of the protocol depends on connection encryption. + +The replica identifies itself to the server using a `clientId` in the form of a UUID. + +### AddVersion + +The request is a `POST` to `/client//add-version/`. +The request body contains the history segment, optionally encoded using any encoding supported by actix-web. +The content-type must be `application/vnd.taskchampion.history-segment`. + +The success response is a 200 OK with an empty body. +The new version ID appears in the `X-Version-Id` header. + +On conflict, the response is a 409 CONFLICT with an empty body. +The expected parent version ID appears in the `X-Parent-Version-Id` header. + +Other error responses (4xx or 5xx) may be returned and should be treated appropriately to their meanings in the HTTP specification. + +### GetChildVersion + +The request is a `GET` to `/client//get-child-version/`. +The response is 404 NOT FOUND if no such version exists. +Otherwise, the response is a 200 OK. +The version's history segment is returned in the response body, with content-type `application/vnd.taskchampion.history-segment`. +The version ID appears in the `X-Version-Id` header. +The response body may be encoded, in accordance with any `Accept-Encoding` header in the request. diff --git a/docs/src/sync.md b/docs/src/sync.md index cd2621cdc..fed75d17f 100644 --- a/docs/src/sync.md +++ b/docs/src/sync.md @@ -1,128 +1,7 @@ -# Synchronization +# Synchronization and the Sync Server -The [task database](./taskdb.md) also implements synchronization. -Synchronization occurs between disconnected replicas, mediated by a server. -The replicas never communicate directly with one another. -The server does not have access to the task data; it sees only opaque blobs of data with a small amount of metadata. +This section covers *synchronization* of *replicas* containing the same set of tasks. +A replica is can perform all operations locally without connecting to a sync server, then share those operations with other replicas when it connects. +Sync is a critical feature of TaskChampion, allowing users to consult and update the same task list on multiple devices, without requiring constant connection. -The synchronization process is a critical part of the task database's functionality, and it cannot function efficiently without occasional synchronization operations - -## Operational Transformations - -Synchronization is based on [operational transformation](https://en.wikipedia.org/wiki/Operational_transformation). -This section will assume some familiarity with the concept. - -## State and Operations - -At a given time, the set of tasks in a replica's storage is the essential "state" of that replica. -All modifications to that state occur via operations, as defined in [Replica Storage](./storage.md). -We can draw a network, or graph, with the nodes representing states and the edges representing operations. -For example: - -```text - o -- State: {abc-d123: 'get groceries', priority L} - | - | -- Operation: set abc-d123 priority to H - | - o -- State: {abc-d123: 'get groceries', priority H} -``` - -For those familiar with distributed version control systems, a state is analogous to a revision, while an operation is analogous to a commit. - -Fundamentally, synchronization involves all replicas agreeing on a single, linear sequence of operations and the state that those operations create. -Since the replicas are not connected, each may have additional operations that have been applied locally, but which have not yet been agreed on. -The synchronization process uses operational transformation to "linearize" those operations. -This process is analogous (vaguely) to rebasing a sequence of Git commits. - -### Versions - -Occasionally, database states are given a name (that takes the form of a UUID). -The system as a whole (all replicas) constructs a branch-free sequence of versions and the operations that separate each version from the next. -The version with the nil UUID is implicitly the empty database. - -The server stores the operations to change a state from a "parent" version to a "child" version, and provides that information as needed to replicas. -Replicas use this information to update their local task databases, and to generate new versions to send to the server. - -Replicas generate a new version to transmit local changes to the server. -The changes are represented as a sequence of operations with the state resulting from the final operation corresponding to the version. -In order to keep the versions in a single sequence, the server will only accept a proposed version from a replica if its parent version matches the latest version on the server. - -In the non-conflict case (such as with a single replica), then, a replica's synchronization process involves gathering up the operations it has accumulated since its last synchronization; bundling those operations into a version; and sending that version to the server. - -### Replica Invariant - -The replica's [storage](./storage.md) contains the current state in `tasks`, the as-yet un-synchronized operations in `operations`, and the last version at which synchronization occurred in `base_version`. - -The replica's un-synchronized operations are already reflected in its local `tasks`, so the following invariant holds: - -> Applying `operations` to the set of tasks at `base_version` gives a set of tasks identical -> to `tasks`. - -### Transformation - -When the latest version on the server contains operations that are not present in the replica, then the states have diverged. -For example: - -```text - o -- version N - w|\a - o o - x| \b - o o - y| \c - o o -- replica's local state - z| - o -- version N+1 -``` - -(diagram notation: `o` designates a state, lower-case letters designate operations, and versions are presented as if they were numbered sequentially) - -In this situation, the replica must "rebase" the local operations onto the latest version from the server and try again. -This process is performed using operational transformation (OT). -The result of this transformation is a sequence of operations based on the latest version, and a sequence of operations the replica can apply to its local task database to reach the same state -Continuing the example above, the resulting operations are shown with `'`: - -```text - o -- version N - w|\a - o o - x| \b - o o - y| \c - o o -- replica's intermediate local state - z| |w' - o-N+1 o - a'\ |x' - o o - b'\ |y' - o o - c'\|z' - o -- version N+2 -``` - -The replica applies w' through z' locally, and sends a' through c' to the server as the operations to generate version N+2. -Either path through this graph, a-b-c-w'-x'-y'-z' or a'-b'-c'-w-x-y-z, must generate *precisely* the same final state at version N+2. -Careful selection of the operations and the transformation function ensure this. - -See the comments in the source code for the details of how this transformation process is implemented. - -## Synchronization Process - -To perform a synchronization, the replica first requests the child version of `base_version` from the server (`get_child_version`). -It applies that version to its local `tasks`, rebases its local `operations` as described above, and updates `base_version`. -The replica repeats this process until the server indicates no additional child versions exist. -If there are no un-synchronized local operations, the process is complete. - -Otherwise, the replica creates a new version containing its local operations, giving its `base_version` as the parent version, and transmits that to the server (`add_version`). -In most cases, this will succeed, but if another replica has created a new version in the interim, then the new version will conflict with that other replica's new version and the server will respond with the new expected parent version. -In this case, the process repeats. -If the server indicates a conflict twice with the same expected base version, that is an indication that the replica has diverged (something serious has gone wrong). - -## Servers - -A replica depends on periodic synchronization for performant operation. -Without synchronization, its list of pending operations would grow indefinitely, and tasks could never be expired. -So all replicas, even "singleton" replicas which do not replicate task data with any other replica, must synchronize periodically. - -TaskChampion provides a `LocalServer` for this purpose. -It implements the `get_child_version` and `add_version` operations as described, storing data on-disk locally, all within the `task` binary. +This is a complex topic, and the section is broken into several chapters, beginning at the lower levels of the implementation and working up. diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index e2df075ed..e9df49ac7 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -7,3 +7,10 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +actix-web = "3.3.0" +failure = "0.1.8" +taskchampion = { path = "../taskchampion" } +futures = "0.3.8" + +[dev-dependencies] +actix-rt = "1.1.1" diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs new file mode 100644 index 000000000..770ce2abc --- /dev/null +++ b/sync-server/src/api/add_version.rs @@ -0,0 +1,188 @@ +use crate::api::{ + failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, + VERSION_ID_HEADER, +}; +use crate::server::{add_version, AddVersionResult, ClientId, VersionId}; +use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result}; +use futures::StreamExt; + +/// Max history segment size: 100MB +const MAX_SIZE: usize = 100 * 1024 * 1024; + +/// Add a new version, after checking prerequisites. The history segment should be transmitted in +/// the request entity body and must have content-type +/// `application/vnd.taskchampion.history-segment`. The content can be encoded in any of the +/// formats supported by actix-web. +/// +/// On success, the response is a 200 OK with the new version ID in the `X-Version-Id` header. If +/// the version cannot be added due to a conflict, the response is a 409 CONFLICT with the expected +/// parent version ID in the `X-Parent-Version-Id` header. +/// +/// Returns other 4xx or 5xx responses on other errors. +#[post("/client/{client_id}/add-version/{parent_version_id}")] +pub(crate) async fn service( + req: HttpRequest, + server_state: web::Data, + web::Path((client_id, parent_version_id)): web::Path<(ClientId, VersionId)>, + mut payload: web::Payload, +) -> Result { + // check content-type + if req.content_type() != HISTORY_SEGMENT_CONTENT_TYPE { + return Err(error::ErrorBadRequest("Bad content-type")); + } + + // read the body in its entirety + let mut body = web::BytesMut::new(); + while let Some(chunk) = payload.next().await { + let chunk = chunk?; + // limit max size of in-memory payload + if (body.len() + chunk.len()) > MAX_SIZE { + return Err(error::ErrorBadRequest("overflow")); + } + body.extend_from_slice(&chunk); + } + + if body.is_empty() { + return Err(error::ErrorBadRequest("Empty body")); + } + + // note that we do not open the transaction until the body has been read + // completely, to avoid blocking other storage access while that data is + // in transit. + let mut txn = server_state.txn().map_err(failure_to_ise)?; + + let client = txn + .get_client(client_id) + .map_err(failure_to_ise)? + .ok_or_else(|| error::ErrorNotFound("no such client"))?; + + let result = add_version(txn, client_id, client, parent_version_id, body.to_vec()) + .map_err(failure_to_ise)?; + Ok(match result { + AddVersionResult::Ok(version_id) => HttpResponse::Ok() + .header(VERSION_ID_HEADER, version_id.to_string()) + .body(""), + AddVersionResult::ExpectedParentVersion(parent_version_id) => HttpResponse::Conflict() + .header(PARENT_VERSION_ID_HEADER, parent_version_id.to_string()) + .body(""), + }) +} + +#[cfg(test)] +mod test { + use crate::api::ServerState; + use crate::app_scope; + use crate::storage::{InMemoryStorage, Storage}; + use actix_web::{http::StatusCode, test, App}; + use taskchampion::Uuid; + + #[actix_rt::test] + async fn test_success() { + let client_id = Uuid::new_v4(); + let version_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let server_box: Box = Box::new(InMemoryStorage::new()); + + // set up the storage contents.. + { + let mut txn = server_box.txn().unwrap(); + txn.set_client_latest_version_id(client_id, Uuid::nil()) + .unwrap(); + } + + let server_state = ServerState::new(server_box); + let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + + let uri = format!("/client/{}/add-version/{}", client_id, parent_version_id); + let req = test::TestRequest::post() + .uri(&uri) + .header( + "Content-Type", + "application/vnd.taskchampion.history-segment", + ) + .set_payload(b"abcd".to_vec()) + .to_request(); + let resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::OK); + + // the returned version ID is random, but let's check that it's not + // the passed parent version ID, at least + let new_version_id = resp.headers().get("X-Version-Id").unwrap(); + assert!(new_version_id != &version_id.to_string()); + + assert_eq!(resp.headers().get("X-Parent-Version-Id"), None); + } + + #[actix_rt::test] + async fn test_conflict() { + let client_id = Uuid::new_v4(); + let version_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let server_box: Box = Box::new(InMemoryStorage::new()); + + // set up the storage contents.. + { + let mut txn = server_box.txn().unwrap(); + txn.set_client_latest_version_id(client_id, version_id) + .unwrap(); + } + + let server_state = ServerState::new(server_box); + let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + + let uri = format!("/client/{}/add-version/{}", client_id, parent_version_id); + let req = test::TestRequest::post() + .uri(&uri) + .header( + "Content-Type", + "application/vnd.taskchampion.history-segment", + ) + .set_payload(b"abcd".to_vec()) + .to_request(); + let resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::CONFLICT); + assert_eq!(resp.headers().get("X-Version-Id"), None); + assert_eq!( + resp.headers().get("X-Parent-Version-Id").unwrap(), + &version_id.to_string() + ); + } + + #[actix_rt::test] + async fn test_bad_content_type() { + let client_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let server_box: Box = Box::new(InMemoryStorage::new()); + let server_state = ServerState::new(server_box); + let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + + let uri = format!("/client/{}/add-version/{}", client_id, parent_version_id); + let req = test::TestRequest::post() + .uri(&uri) + .header("Content-Type", "not/correct") + .set_payload(b"abcd".to_vec()) + .to_request(); + let resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } + + #[actix_rt::test] + async fn test_empty_body() { + let client_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let server_box: Box = Box::new(InMemoryStorage::new()); + let server_state = ServerState::new(server_box); + let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + + let uri = format!("/client/{}/add-version/{}", client_id, parent_version_id); + let req = test::TestRequest::post() + .uri(&uri) + .header( + "Content-Type", + "application/vnd.taskchampion.history-segment", + ) + .to_request(); + let resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } +} diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs new file mode 100644 index 000000000..83f9e2224 --- /dev/null +++ b/sync-server/src/api/get_child_version.rs @@ -0,0 +1,138 @@ +use crate::api::{ + failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, + VERSION_ID_HEADER, +}; +use crate::server::{get_child_version, ClientId, VersionId}; +use actix_web::{error, get, web, HttpResponse, Result}; + +/// Get a child version. +/// +/// On succcess, the response is the same sequence of bytes originally sent to the server, +/// with content-type `application/vnd.taskchampion.history-segment`. The `X-Version-Id` and +/// `X-Parent-Version-Id` headers contain the corresponding values. +/// +/// If no such child exists, returns a 404 with no content. +/// Returns other 4xx or 5xx responses on other errors. +#[get("/client/{client_id}/get-child-version/{parent_version_id}")] +pub(crate) async fn service( + server_state: web::Data, + web::Path((client_id, parent_version_id)): web::Path<(ClientId, VersionId)>, +) -> Result { + let mut txn = server_state.txn().map_err(failure_to_ise)?; + + txn.get_client(client_id) + .map_err(failure_to_ise)? + .ok_or_else(|| error::ErrorNotFound("no such client"))?; + + let result = get_child_version(txn, client_id, parent_version_id).map_err(failure_to_ise)?; + if let Some(result) = result { + Ok(HttpResponse::Ok() + .content_type(HISTORY_SEGMENT_CONTENT_TYPE) + .header(VERSION_ID_HEADER, result.version_id.to_string()) + .header( + PARENT_VERSION_ID_HEADER, + result.parent_version_id.to_string(), + ) + .body(result.history_segment)) + } else { + Err(error::ErrorNotFound("no such version")) + } +} + +#[cfg(test)] +mod test { + use crate::api::ServerState; + use crate::app_scope; + use crate::storage::{InMemoryStorage, Storage}; + use actix_web::{http::StatusCode, test, App}; + use taskchampion::Uuid; + + #[actix_rt::test] + async fn test_success() { + let client_id = Uuid::new_v4(); + let version_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let server_box: Box = Box::new(InMemoryStorage::new()); + + // set up the storage contents.. + { + let mut txn = server_box.txn().unwrap(); + txn.set_client_latest_version_id(client_id, Uuid::new_v4()) + .unwrap(); + txn.add_version(client_id, version_id, parent_version_id, b"abcd".to_vec()) + .unwrap(); + } + + let server_state = ServerState::new(server_box); + let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + + let uri = format!( + "/client/{}/get-child-version/{}", + client_id, parent_version_id + ); + let req = test::TestRequest::get().uri(&uri).to_request(); + let mut resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get("X-Version-Id").unwrap(), + &version_id.to_string() + ); + assert_eq!( + resp.headers().get("X-Parent-Version-Id").unwrap(), + &parent_version_id.to_string() + ); + assert_eq!( + resp.headers().get("Content-Type").unwrap(), + &"application/vnd.taskchampion.history-segment".to_string() + ); + + use futures::StreamExt; + let (bytes, _) = resp.take_body().into_future().await; + assert_eq!(bytes.unwrap().unwrap().as_ref(), b"abcd"); + } + + #[actix_rt::test] + async fn test_client_not_found() { + let client_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let server_box: Box = Box::new(InMemoryStorage::new()); + let server_state = ServerState::new(server_box); + let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + + let uri = format!( + "/client/{}/get-child-version/{}", + client_id, parent_version_id + ); + let req = test::TestRequest::get().uri(&uri).to_request(); + let resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + assert_eq!(resp.headers().get("X-Version-Id"), None); + assert_eq!(resp.headers().get("X-Parent-Version-Id"), None); + } + + #[actix_rt::test] + async fn test_version_not_found() { + let client_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let server_box: Box = Box::new(InMemoryStorage::new()); + + // create the client, but not the version + { + let mut txn = server_box.txn().unwrap(); + txn.set_client_latest_version_id(client_id, Uuid::new_v4()) + .unwrap(); + } + let server_state = ServerState::new(server_box); + let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + + let uri = format!( + "/client/{}/get-child-version/{}", + client_id, parent_version_id + ); + let req = test::TestRequest::get().uri(&uri).to_request(); + let resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + assert_eq!(resp.headers().get("X-Version-Id"), None); + assert_eq!(resp.headers().get("X-Parent-Version-Id"), None); + } +} diff --git a/sync-server/src/api/mod.rs b/sync-server/src/api/mod.rs new file mode 100644 index 000000000..cddcab59c --- /dev/null +++ b/sync-server/src/api/mod.rs @@ -0,0 +1,30 @@ +use crate::storage::Storage; +use actix_web::{error, http::StatusCode, web, Scope}; +use std::sync::Arc; + +mod add_version; +mod get_child_version; + +/// The content-type for history segments (opaque blobs of bytes) +pub(crate) const HISTORY_SEGMENT_CONTENT_TYPE: &str = + "application/vnd.taskchampion.history-segment"; + +/// The header names for version ID +pub(crate) const VERSION_ID_HEADER: &str = "X-Version-Id"; + +/// The header names for parent version ID +pub(crate) const PARENT_VERSION_ID_HEADER: &str = "X-Parent-Version-Id"; + +/// The type containing a reference to the Storage object in the Actix state. +pub(crate) type ServerState = Arc>; + +pub(crate) fn api_scope() -> Scope { + web::scope("") + .service(get_child_version::service) + .service(add_version::service) +} + +/// Convert a failure::Error to an Actix ISE +fn failure_to_ise(err: failure::Error) -> impl actix_web::ResponseError { + error::InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR) +} diff --git a/sync-server/src/lib.rs b/sync-server/src/lib.rs deleted file mode 100644 index e85125ed0..000000000 --- a/sync-server/src/lib.rs +++ /dev/null @@ -1,86 +0,0 @@ -#![allow(clippy::new_without_default)] - -use std::collections::HashMap; - -type Blob = Vec; - -struct User { - // versions, indexed at v-1 - versions: Vec, - snapshots: HashMap, -} - -pub struct Server { - users: HashMap, -} - -pub enum VersionAdd { - // OK, version added - Ok, - // Rejected, must be based on the the given version - ExpectedVersion(u64), -} - -impl User { - fn new() -> User { - User { - versions: vec![], - snapshots: HashMap::new(), - } - } - - fn get_versions(&self, since_version: u64) -> Vec { - let last_version = self.versions.len(); - if last_version == since_version as usize { - return vec![]; - } - self.versions[since_version as usize..last_version].to_vec() - } - - fn add_version(&mut self, version: u64, blob: Blob) -> VersionAdd { - // of by one here: client wants to send version 1 first - let expected_version = self.versions.len() as u64 + 1; - if version != expected_version { - return VersionAdd::ExpectedVersion(expected_version); - } - self.versions.push(blob); - - VersionAdd::Ok - } - - fn add_snapshot(&mut self, version: u64, blob: Blob) { - self.snapshots.insert(version, blob); - } -} - -impl Server { - pub fn new() -> Server { - Server { - users: HashMap::new(), - } - } - - fn get_user_mut(&mut self, username: &str) -> &mut User { - self.users - .entry(username.to_string()) - .or_insert_with(User::new) - } - - /// Get a vector of all versions after `since_version` - pub fn get_versions(&self, username: &str, since_version: u64) -> Vec { - self.users - .get(username) - .map(|user| user.get_versions(since_version)) - .unwrap_or_default() - } - - /// Add a new version. If the given version number is incorrect, this responds with the - /// appropriate version and expects the caller to try again. - pub fn add_version(&mut self, username: &str, version: u64, blob: Blob) -> VersionAdd { - self.get_user_mut(username).add_version(version, blob) - } - - pub fn add_snapshot(&mut self, username: &str, version: u64, blob: Blob) { - self.get_user_mut(username).add_snapshot(version, blob); - } -} diff --git a/sync-server/src/main.rs b/sync-server/src/main.rs new file mode 100644 index 000000000..7b91c5e3c --- /dev/null +++ b/sync-server/src/main.rs @@ -0,0 +1,54 @@ +use crate::storage::{InMemoryStorage, Storage}; +use actix_web::{get, web, App, HttpServer, Responder, Scope}; +use api::{api_scope, ServerState}; + +mod api; +mod server; +mod storage; + +// TODO: use hawk to sign requests + +#[get("/")] +async fn index() -> impl Responder { + // TODO: add version here + "TaskChampion sync server" +} + +/// Return a scope defining the URL rules for this server, with access to +/// the given ServerState. +pub(crate) fn app_scope(server_state: ServerState) -> Scope { + web::scope("") + .data(server_state) + .service(index) + .service(api_scope()) +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + let server_box: Box = Box::new(InMemoryStorage::new()); + let server_state = ServerState::new(server_box); + + HttpServer::new(move || App::new().service(app_scope(server_state.clone()))) + .bind("127.0.0.1:8080")? + .run() + .await +} + +#[cfg(test)] +mod test { + use super::*; + use crate::api::ServerState; + use crate::storage::{InMemoryStorage, Storage}; + use actix_web::{test, App}; + + #[actix_rt::test] + async fn test_index_get() { + let server_box: Box = Box::new(InMemoryStorage::new()); + let server_state = ServerState::new(server_box); + let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + + let req = test::TestRequest::get().uri("/").to_request(); + let resp = test::call_service(&mut app, req).await; + assert!(resp.status().is_success()); + } +} diff --git a/sync-server/src/server.rs b/sync-server/src/server.rs new file mode 100644 index 000000000..0a404172d --- /dev/null +++ b/sync-server/src/server.rs @@ -0,0 +1,197 @@ +//! This module implements the core logic of the server: handling transactions, upholding +//! invariants, and so on. +use crate::storage::{Client, StorageTxn}; +use failure::Fallible; +use taskchampion::Uuid; + +/// The distinguished value for "no version" +pub const NO_VERSION_ID: VersionId = Uuid::nil(); + +pub(crate) type HistorySegment = Vec; +pub(crate) type ClientId = Uuid; +pub(crate) type VersionId = Uuid; + +/// Response to get_child_version +#[derive(Clone, PartialEq, Debug)] +pub(crate) struct GetVersionResult { + pub(crate) version_id: Uuid, + pub(crate) parent_version_id: Uuid, + pub(crate) history_segment: HistorySegment, +} + +pub(crate) fn get_child_version<'a>( + mut txn: Box, + client_id: ClientId, + parent_version_id: VersionId, +) -> Fallible> { + Ok(txn + .get_version_by_parent(client_id, parent_version_id)? + .map(|version| GetVersionResult { + version_id: version.version_id, + parent_version_id: version.parent_version_id, + history_segment: version.history_segment, + })) +} + +/// Response to add_version +#[derive(Clone, PartialEq, Debug)] +pub(crate) enum AddVersionResult { + /// OK, version added with the given ID + Ok(VersionId), + /// Rejected; expected a version with the given parent version + ExpectedParentVersion(VersionId), +} + +pub(crate) fn add_version<'a>( + mut txn: Box, + client_id: ClientId, + client: Client, + parent_version_id: VersionId, + history_segment: HistorySegment, +) -> Fallible { + // check if this version is acceptable, under the protection of the transaction + if client.latest_version_id != NO_VERSION_ID && parent_version_id != client.latest_version_id { + return Ok(AddVersionResult::ExpectedParentVersion( + client.latest_version_id, + )); + } + + // invent a version ID + let version_id = Uuid::new_v4(); + + // update the DB + txn.add_version(client_id, version_id, parent_version_id, history_segment)?; + txn.set_client_latest_version_id(client_id, version_id)?; + txn.commit()?; + + Ok(AddVersionResult::Ok(version_id)) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::storage::{InMemoryStorage, Storage}; + + #[test] + fn gcv_not_found() -> Fallible<()> { + let storage = InMemoryStorage::new(); + let txn = storage.txn()?; + let client_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + assert_eq!(get_child_version(txn, client_id, parent_version_id)?, None); + Ok(()) + } + + #[test] + fn gcv_found() -> Fallible<()> { + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let client_id = Uuid::new_v4(); + let version_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let history_segment = b"abcd".to_vec(); + + txn.add_version( + client_id, + version_id, + parent_version_id, + history_segment.clone(), + )?; + + assert_eq!( + get_child_version(txn, client_id, parent_version_id)?, + Some(GetVersionResult { + version_id, + parent_version_id, + history_segment, + }) + ); + Ok(()) + } + + #[test] + fn av_conflict() -> Fallible<()> { + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let client_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let history_segment = b"abcd".to_vec(); + let existing_parent_version_id = Uuid::new_v4(); + let client = Client { + latest_version_id: existing_parent_version_id, + }; + + assert_eq!( + add_version( + txn, + client_id, + client, + parent_version_id, + history_segment.clone() + )?, + AddVersionResult::ExpectedParentVersion(existing_parent_version_id) + ); + + // verify that the storage wasn't updated + txn = storage.txn()?; + assert_eq!(txn.get_client(client_id)?, None); + assert_eq!( + txn.get_version_by_parent(client_id, parent_version_id)?, + None + ); + + Ok(()) + } + + fn test_av_success(latest_version_id_nil: bool) -> Fallible<()> { + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let client_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let history_segment = b"abcd".to_vec(); + let client = Client { + latest_version_id: if latest_version_id_nil { + Uuid::nil() + } else { + parent_version_id + }, + }; + + let result = add_version( + txn, + client_id, + client, + parent_version_id, + history_segment.clone(), + )?; + if let AddVersionResult::Ok(new_version_id) = result { + // check that it invented a new version ID + assert!(new_version_id != parent_version_id); + + // verify that the storage was updated + txn = storage.txn()?; + let client = txn.get_client(client_id)?.unwrap(); + assert_eq!(client.latest_version_id, new_version_id); + let version = txn + .get_version_by_parent(client_id, parent_version_id)? + .unwrap(); + assert_eq!(version.version_id, new_version_id); + assert_eq!(version.parent_version_id, parent_version_id); + assert_eq!(version.history_segment, history_segment); + } else { + panic!("did not get Ok from add_version"); + } + + Ok(()) + } + + #[test] + fn av_success_with_existing_history() -> Fallible<()> { + test_av_success(true) + } + + #[test] + fn av_success_nil_latest_version_id() -> Fallible<()> { + test_av_success(false) + } +} diff --git a/sync-server/src/storage/inmemory.rs b/sync-server/src/storage/inmemory.rs new file mode 100644 index 000000000..91c868d42 --- /dev/null +++ b/sync-server/src/storage/inmemory.rs @@ -0,0 +1,90 @@ +use super::{Client, Storage, StorageTxn, Uuid, Version}; +use failure::Fallible; +use std::collections::HashMap; +use std::sync::{Mutex, MutexGuard}; + +struct Inner { + /// Clients, indexed by client_id + clients: HashMap, + + /// Versions, indexed by (client_id, parent_version_id) + versions: HashMap<(Uuid, Uuid), Version>, +} + +pub(crate) struct InMemoryStorage(Mutex); + +impl InMemoryStorage { + pub(crate) fn new() -> Self { + Self(Mutex::new(Inner { + clients: HashMap::new(), + versions: HashMap::new(), + })) + } +} + +struct InnerTxn<'a>(MutexGuard<'a, Inner>); + +/// In-memory storage for testing and experimentation. +/// +/// NOTE: this does not implement transaction rollback. +impl Storage for InMemoryStorage { + fn txn<'a>(&'a self) -> Fallible> { + Ok(Box::new(InnerTxn(self.0.lock().expect("poisoned lock")))) + } +} + +impl<'a> StorageTxn for InnerTxn<'a> { + fn get_client(&mut self, client_id: Uuid) -> Fallible> { + Ok(self.0.clients.get(&client_id).cloned()) + } + + fn set_client_latest_version_id( + &mut self, + client_id: Uuid, + latest_version_id: Uuid, + ) -> Fallible<()> { + if let Some(client) = self.0.clients.get_mut(&client_id) { + client.latest_version_id = latest_version_id; + } else { + self.0 + .clients + .insert(client_id, Client { latest_version_id }); + } + Ok(()) + } + + fn get_version_by_parent( + &mut self, + client_id: Uuid, + parent_version_id: Uuid, + ) -> Fallible> { + Ok(self + .0 + .versions + .get(&(client_id, parent_version_id)) + .cloned()) + } + + fn add_version( + &mut self, + client_id: Uuid, + version_id: Uuid, + parent_version_id: Uuid, + history_segment: Vec, + ) -> Fallible<()> { + // TODO: verify it doesn't exist (`.entry`?) + let version = Version { + version_id, + parent_version_id, + history_segment, + }; + self.0 + .versions + .insert((client_id, version.parent_version_id), version); + Ok(()) + } + + fn commit(&mut self) -> Fallible<()> { + Ok(()) + } +} diff --git a/sync-server/src/storage/mod.rs b/sync-server/src/storage/mod.rs new file mode 100644 index 000000000..2915f3c4d --- /dev/null +++ b/sync-server/src/storage/mod.rs @@ -0,0 +1,56 @@ +use failure::Fallible; +use taskchampion::Uuid; + +mod inmemory; +pub(crate) use inmemory::InMemoryStorage; + +#[derive(Clone, PartialEq, Debug)] +pub(crate) struct Client { + pub(crate) latest_version_id: Uuid, +} + +#[derive(Clone, PartialEq, Debug)] +pub(crate) struct Version { + pub(crate) version_id: Uuid, + pub(crate) parent_version_id: Uuid, + pub(crate) history_segment: Vec, +} + +pub(crate) trait StorageTxn { + /// Get information about the given client + fn get_client(&mut self, client_id: Uuid) -> Fallible>; + + /// Set the client's latest_version_id (creating the client if necessary) + fn set_client_latest_version_id( + &mut self, + client_id: Uuid, + latest_version_id: Uuid, + ) -> Fallible<()>; + + /// Get a version, indexed by parent version id + fn get_version_by_parent( + &mut self, + client_id: Uuid, + parent_version_id: Uuid, + ) -> Fallible>; + + /// Add a version (that must not already exist) + fn add_version( + &mut self, + client_id: Uuid, + version_id: Uuid, + parent_version_id: Uuid, + history_segment: Vec, + ) -> Fallible<()>; + + /// Commit any changes made in the transaction. It is an error to call this more than + /// once. It is safe to skip this call for read-only operations. + fn commit(&mut self) -> Fallible<()>; +} + +/// A trait for objects able to act as storage. Most of the interesting behavior is in the +/// [`crate::storage::StorageTxn`] trait. +pub(crate) trait Storage: Send + Sync { + /// Begin a transaction + fn txn<'a>(&'a self) -> Fallible>; +}