Compare commits

..

No commits in common. "94dc1c063c14ddf43a04c4d9a6c64ad3160b3bdf" and "6a0ff0d8a241f6fd49ebc8382d4eddb222478013" have entirely different histories.

25 changed files with 401 additions and 926 deletions

256
Cargo.lock generated
View File

@ -69,9 +69,9 @@ dependencies = [
[[package]]
name = "anstream"
version = "0.6.11"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
checksum = "bff2cf94a3dbe2d57cbd56485e1bd7436455058034d6c2d47be51d4e5e4bc6ab"
dependencies = [
"anstyle",
"anstyle-parse",
@ -102,17 +102,17 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
checksum = "0238ca56c96dfa37bdf7c373c8886dd591322500aceeeccdb2216fe06dc2f796"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
"windows-sys",
]
[[package]]
@ -147,9 +147,9 @@ dependencies = [
[[package]]
name = "base64"
version = "0.21.7"
version = "0.21.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
checksum = "c79fed4cdb43e993fcdadc7e58a09fd0e3e649c4436fa11da71c9f1f3ee7feb9"
[[package]]
name = "base64ct"
@ -219,9 +219,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.34"
version = "0.4.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b"
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
dependencies = [
"android-tzdata",
"iana-time-zone",
@ -229,7 +229,7 @@ dependencies = [
"num-traits",
"serde",
"wasm-bindgen",
"windows-targets 0.52.0",
"windows-targets",
]
[[package]]
@ -244,9 +244,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.2"
version = "4.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651"
checksum = "33e92c5c1a78c62968ec57dbc2440366a2d6e5a23faf829970ff1585dc6b18e2"
dependencies = [
"clap_builder",
"clap_derive",
@ -254,21 +254,21 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.2"
version = "4.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
checksum = "f4323769dc8a61e2c39ad7dc26f6f2800524691a44d74fe3d1071a5c24db6370"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim 0.11.0",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.0"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47"
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
dependencies = [
"heck",
"proc-macro2",
@ -278,9 +278,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.7.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
[[package]]
name = "cloudabi"
@ -445,7 +445,7 @@ dependencies = [
"ident_case",
"proc-macro2",
"quote",
"strsim 0.10.0",
"strsim",
"syn",
]
@ -544,7 +544,7 @@ checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@ -617,9 +617,9 @@ checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "futures"
version = "0.3.30"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335"
dependencies = [
"futures-channel",
"futures-core",
@ -632,9 +632,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.30"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb"
dependencies = [
"futures-core",
"futures-sink",
@ -642,15 +642,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.30"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
[[package]]
name = "futures-executor"
version = "0.3.30"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
dependencies = [
"futures-core",
"futures-task",
@ -659,15 +659,15 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.30"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
[[package]]
name = "futures-macro"
version = "0.3.30"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
dependencies = [
"proc-macro2",
"quote",
@ -676,21 +676,21 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.30"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
[[package]]
name = "futures-task"
version = "0.3.30"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
[[package]]
name = "futures-util"
version = "0.3.30"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104"
dependencies = [
"futures-channel",
"futures-core",
@ -984,9 +984,9 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]]
name = "js-sys"
version = "0.3.69"
version = "0.3.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca"
dependencies = [
"wasm-bindgen",
]
@ -1131,7 +1131,6 @@ dependencies = [
name = "lox_utils"
version = "0.1.0"
dependencies = [
"chrono",
"lox-library",
"serde",
"serde_json",
@ -1188,7 +1187,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@ -1244,12 +1243,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num-integer"
version = "0.1.45"
@ -1412,7 +1405,7 @@ dependencies = [
"libc",
"redox_syscall 0.3.5",
"smallvec",
"windows-targets 0.48.1",
"windows-targets",
]
[[package]]
@ -1505,9 +1498,9 @@ dependencies = [
[[package]]
name = "prometheus-client"
version = "0.22.2"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1ca959da22a332509f2a73ae9e5f23f9dcfc31fd3a54d71f159495bd5909baa"
checksum = "510c4f1c9d81d556458f94c98f857748130ea9737bbd6053da497503b26ea63c"
dependencies = [
"dtoa",
"itoa",
@ -1828,7 +1821,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@ -1843,7 +1836,7 @@ version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
dependencies = [
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@ -1883,18 +1876,18 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
[[package]]
name = "serde"
version = "1.0.197"
version = "1.0.195"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.197"
version = "1.0.195"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
dependencies = [
"proc-macro2",
"quote",
@ -1903,9 +1896,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.113"
version = "1.0.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
dependencies = [
"itoa",
"ryu",
@ -1926,9 +1919,9 @@ dependencies = [
[[package]]
name = "serde_with"
version = "3.7.0"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a"
checksum = "f58c3a1b3e418f61c25b2aeb43fc6c95eaa252b8cecdda67f401943e9e08d33f"
dependencies = [
"base64",
"chrono",
@ -1936,7 +1929,6 @@ dependencies = [
"indexmap 1.9.3",
"indexmap 2.0.0",
"serde",
"serde_derive",
"serde_json",
"serde_with_macros",
"time",
@ -1944,9 +1936,9 @@ dependencies = [
[[package]]
name = "serde_with_macros"
version = "3.7.0"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655"
checksum = "d2068b437a31fc68f25dd7edc296b078f04b45145c199d8eed9866e45f1ff274"
dependencies = [
"darling",
"proc-macro2",
@ -2058,12 +2050,6 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strsim"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
[[package]]
name = "subtle"
version = "2.5.0"
@ -2091,23 +2077,23 @@ dependencies = [
"fastrand",
"redox_syscall 0.3.5",
"rustix",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
name = "thiserror"
version = "1.0.57"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.57"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
dependencies = [
"proc-macro2",
"quote",
@ -2116,13 +2102,12 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.34"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e"
dependencies = [
"deranged",
"itoa",
"num-conv",
"powerfmt",
"serde",
"time-core",
@ -2137,11 +2122,10 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.17"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774"
checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f"
dependencies = [
"num-conv",
"time-core",
]
@ -2177,7 +2161,7 @@ dependencies = [
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@ -2347,9 +2331,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.92"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
@ -2357,9 +2341,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.92"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
dependencies = [
"bumpalo",
"log",
@ -2384,9 +2368,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.92"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -2394,9 +2378,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.92"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [
"proc-macro2",
"quote",
@ -2407,9 +2391,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.92"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
[[package]]
name = "wasm-streams"
@ -2462,7 +2446,7 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
dependencies = [
"windows-targets 0.48.1",
"windows-targets",
]
[[package]]
@ -2471,16 +2455,7 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.1",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
"windows-targets",
]
[[package]]
@ -2489,28 +2464,13 @@ version = "0.48.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
dependencies = [
"windows_aarch64_gnullvm 0.48.0",
"windows_aarch64_msvc 0.48.0",
"windows_i686_gnu 0.48.0",
"windows_i686_msvc 0.48.0",
"windows_x86_64_gnu 0.48.0",
"windows_x86_64_gnullvm 0.48.0",
"windows_x86_64_msvc 0.48.0",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
@ -2519,84 +2479,42 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winreg"
version = "0.10.1"

View File

@ -16,25 +16,25 @@ readme = "README.md"
[dependencies]
julianday = "1.2.0"
base64 = "0.21.7"
base64 = "0.21.6"
hyper = { version = "0.14.28", features = ["deprecated", "backports","server"] }
hex_fmt = "0.3"
futures = "0.3.30"
time = "0.3.34"
futures = "0.3.29"
time = "0.3.31"
tokio = { version = "1", features = ["full", "macros", "signal"] }
rand = "0.8.5"
reqwest = { version = "0.11", features = ["json", "stream"]}
serde = { version = "1.0", features = ["derive", "rc"] }
serde_with = "3.7.0"
serde_with = "3.5.0"
lox-zkp = { git = "https://gitlab.torproject.org/onyinyang/lox-zkp", version = "0.8.0" }
lox-library = { path = "../lox-library", version = "0.1.0"}
lox_utils = { path = "../lox-utils", version = "0.1.0"}
rdsys_backend = { path = "../rdsys-backend-api", version = "0.2"}
clap = { version = "4.5.2", features = ["derive"] }
serde_json = "1.0.113"
clap = { version = "4.4.14", features = ["derive"] }
serde_json = "1.0.108"
prometheus = "0.13.3"
sled = "0.34.7"
prometheus-client = "0.22.2"
prometheus-client = "0.22.0"
thiserror = "1"
curve25519-dalek = { version = "4", default-features = false, features = ["serde", "rand_core", "digest"] }
troll-patrol = { git = "https://git-crysp.uwaterloo.ca/vvecna/troll-patrol", version = "0.1.0" }
@ -42,5 +42,5 @@ array-bytes = "6.2.0"
sha1 = "0.10"
[dependencies.chrono]
version = "0.4.34"
version = "0.4.31"
features = ["serde"]

File diff suppressed because one or more lines are too long

View File

@ -7,7 +7,7 @@ use lox_library::{BridgeAuth, BridgeDb};
use sled::IVec;
use std::collections::HashMap;
use thiserror::Error;
use troll_patrol::bridge_verification_info::BridgeVerificationInfo;
use troll_patrol::bridge_info::BridgeInfo as TPBridgeInfo;
#[derive(Error, Debug)]
pub enum LoxDBError {
@ -80,10 +80,9 @@ impl DB {
ba: Arc::new(Mutex::new(new_ba)),
extra_bridges: Arc::new(Mutex::new(Vec::new())),
to_be_replaced_bridges: Arc::new(Mutex::new(Vec::new())),
tp_bridge_infos: Arc::new(Mutex::new(HashMap::<
[u8; 20],
BridgeVerificationInfo,
>::new())),
tp_bridge_infos: Arc::new(Mutex::new(
HashMap::<[u8; 20], TPBridgeInfo>::new(),
)),
metrics,
};
}

View File

@ -4,7 +4,7 @@
use curve25519_dalek::{ristretto::RistrettoBasepointTable, Scalar};
use hyper::{body::Bytes, header::HeaderValue, Body, Response};
use lox_library::{
bridge_table::{self, BridgeLine, EncryptedBucket, MAX_BRIDGES_PER_BUCKET},
bridge_table::{BridgeLine, EncryptedBucket, MAX_BRIDGES_PER_BUCKET},
proto::{
blockage_migration, check_blockage, issue_invite, level_up, migration, open_invite,
positive_report, redeem_invite, trust_promotion,
@ -16,16 +16,15 @@ use serde::{Deserialize, Serialize};
use lox_zkp::ProofError;
use serde_json::json;
use sha1::{Digest, Sha1};
use std::{
cmp::Ordering,
collections::{BTreeMap, HashMap, HashSet},
collections::{BTreeMap, HashMap},
ops::DerefMut,
sync::{Arc, Mutex},
};
use troll_patrol::{
self, bridge_verification_info::BridgeVerificationInfo, negative_report::*, positive_report::*,
self, bridge_info::BridgeInfo as TPBridgeInfo, negative_report::*, positive_report::*,
};
use crate::metrics::Metrics;
@ -38,7 +37,7 @@ pub struct LoxServerContext {
pub extra_bridges: Arc<Mutex<Vec<BridgeLine>>>,
pub to_be_replaced_bridges: Arc<Mutex<Vec<BridgeLine>>>,
// Map of bridge fingerprint to values needed to verify TP reports
pub tp_bridge_infos: Arc<Mutex<HashMap<[u8; 20], BridgeVerificationInfo>>>,
pub tp_bridge_infos: Arc<Mutex<HashMap<[u8; 20], TPBridgeInfo>>>,
#[serde(skip)]
pub metrics: Metrics,
}
@ -67,8 +66,6 @@ impl LoxServerContext {
for bucket in open_invitations {
self.add_openinv_bucket(*bucket)
}
self.generate_tp_bridge_infos();
}
pub fn handle_working_resources(
@ -125,14 +122,11 @@ impl LoxServerContext {
for bridge in blocked {
let res = self.mark_blocked(bridge);
if res {
println!(
"Blocked BridgeLine {:?} successfully marked unreachable",
bridge
);
println!("BridgeLine {:?} successfully marked unreachable", bridge);
self.metrics.blocked_bridges.inc();
} else {
println!(
"Blocked BridgeLine {:?} NOT marked unreachable, not found in bridgetable!",
"BridgeLine {:?} NOT marked unreachable, not found in bridgetable!",
bridge.uid_fingerprint
);
}
@ -142,22 +136,20 @@ impl LoxServerContext {
let res = self.update_bridge(bridge);
if res {
println!(
"Grace period BridgeLine {:?} successfully updated.",
"BridgeLine {:?} successfully updated.",
bridge.uid_fingerprint
);
accounted_for_bridges.push(bridge.uid_fingerprint);
self.metrics.existing_or_updated_bridges.inc();
} else {
println!("Grace period BridgeLine: {:?} not found in Lox's Bridgetable. Wait until it is working to update/add it!", bridge.uid_fingerprint);
}
}
// Next, handle the failing bridges. If resource last passed tests >= ACCEPTED_HOURS_OF_FAILURE ago,
// Next, handle the failing bridges. If resource last passed tests >=ACCEPTED_HOURS_OF_FAILURE ago,
// it should be replaced with a working resource and be removed from the bridgetable.
for bridge in failing {
let res = self.replace_with_new(bridge);
if res == lox_library::ReplaceSuccess::Replaced {
println!(
"Failing BridgeLine {:?} successfully replaced.",
"BridgeLine {:?} successfully replaced.",
bridge.uid_fingerprint
);
accounted_for_bridges.push(bridge.uid_fingerprint);
@ -166,9 +158,10 @@ impl LoxServerContext {
// Add the bridge to the list of to_be_replaced bridges in the Lox context and try
// again to replace at the next update (nothing changes in the Lox Authority)
println!(
"Failing BridgeLine {:?} NOT replaced, saved for next update!",
"BridgeLine {:?} NOT replaced, saved for next update!",
bridge.uid_fingerprint
);
self.new_to_be_replaced_bridge(bridge);
self.metrics.existing_or_updated_bridges.inc();
accounted_for_bridges.push(bridge.uid_fingerprint);
} else {
@ -178,7 +171,7 @@ impl LoxServerContext {
"ReplaceSuccess incorrectly set"
);
println!(
"Failing BridgeLine {:?} not found in bridge table.",
"BridgeLine {:?} no longer in bridge table.",
bridge.uid_fingerprint
);
}
@ -216,9 +209,11 @@ impl LoxServerContext {
self.metrics.removed_bridges.inc();
}
lox_library::ReplaceSuccess::NotReplaced => {
// Try again to replace at the next update (nothing changes in the Lox Authority)
// Add the bridge to the list of to_be_replaced bridges in the Lox context and try
// again to replace at the next update (nothing changes in the Lox Authority)
println!("BridgeLine {:?} not found in rdsys update NOT replaced, saved for next update!",
bridgeline.uid_fingerprint);
self.new_to_be_replaced_bridge(bridgeline);
self.metrics.existing_or_updated_bridges.inc();
}
lox_library::ReplaceSuccess::NotFound => println!(
@ -248,9 +243,6 @@ impl LoxServerContext {
}
}
}
// Regenerate tables for verifying TP reports
self.generate_tp_bridge_infos();
}
pub fn append_extra_bridges(&self, bridge: BridgeLine) {
@ -269,6 +261,17 @@ impl LoxServerContext {
return_bridges
}
pub fn remove_single_bridge(&self) {
let mut extra_bridges = self.extra_bridges.lock().unwrap();
let length = extra_bridges.len();
_ = extra_bridges.remove(length - 1)
}
pub fn new_to_be_replaced_bridge(&self, bridge: BridgeLine) {
let mut to_be_replaced_bridges = self.to_be_replaced_bridges.lock().unwrap();
to_be_replaced_bridges.push(bridge);
}
// Add extra_bridges to the Lox bridge table as open invitation bridges
// TODO: Add some consideration for whether or not bridges should be sorted as
// open invitation buckets or hot spare buckets
@ -313,14 +316,13 @@ impl LoxServerContext {
// available bridges or from a spare bucket
pub fn replace_with_new(&self, bridgeline: BridgeLine) -> lox_library::ReplaceSuccess {
let mut ba_obj = self.ba.lock().unwrap();
let mut eb_obj = self.extra_bridges.lock().unwrap();
let available_bridge = eb_obj.pop();
let eb_obj = self.extra_bridges.lock().unwrap();
let available_bridge = eb_obj.last();
let result = ba_obj.bridge_replace(&bridgeline, available_bridge);
if result != lox_library::ReplaceSuccess::Replaced {
if let Some(bridge) = available_bridge {
// If available bridge wasn't removed, return it
eb_obj.push(bridge);
}
// .last() doesn't actually remove the object so we still have to do that if the bridge was
// replaced with an available bridge
if result == lox_library::ReplaceSuccess::Replaced && eb_obj.len() > 0 {
self.remove_single_bridge();
}
result
}
@ -340,53 +342,6 @@ impl LoxServerContext {
ba_obj.bridge_update(&bridgeline)
}
// (Re)generate the information needed to verify Troll Patrol reports
pub fn generate_tp_bridge_infos(&self) {
let la_obj = self.ba.lock().unwrap();
// Recompute table
let mut tp_bridge_infos = self.tp_bridge_infos.lock().unwrap();
tp_bridge_infos.clear();
// Go through all buckets and all bridges in buckets, map bridge to
// buckets containing it. Note that a bridge may be contained within
// multiple buckets (open invitation buckets and invite-only buckets).
let buckets = &la_obj.bridge_table.buckets;
for id in buckets.keys() {
let bridges = buckets.get(id).unwrap();
let key = la_obj.bridge_table.keys.get(id).unwrap();
let bucket = bridge_table::to_scalar(*id, key);
for bridge in bridges {
if bridge.fingerprint != [0; 20] {
// Get hashed fingerprint
let mut hasher = Sha1::new();
hasher.update(&bridge.fingerprint);
let fingerprint: [u8; 20] = hasher.finalize().into();
// Add bucket to existing entry or add new entry
if tp_bridge_infos.contains_key(&fingerprint) {
tp_bridge_infos
.get_mut(&fingerprint)
.unwrap()
.buckets
.insert(bucket);
} else {
let mut buckets = HashSet::<Scalar>::new();
buckets.insert(bucket);
tp_bridge_infos.insert(
fingerprint,
BridgeVerificationInfo {
bridge_line: *bridge,
buckets: buckets,
pubkey: None, // TODO: add pubkey for signed bridge tokens
},
);
}
}
}
}
}
//#[cfg(test)]
/// For testing only: manually advance the day by the given number
/// of days.
@ -794,34 +749,6 @@ impl LoxServerContext {
// Troll Patrol-related tasks
// Troll Patrol reports that a bridge is blocked somewhere
pub fn report_blocked_bridges(self, request: Bytes) -> Response<Body> {
let blocked_bridges: HashMap<String, HashSet<String>> =
match serde_json::from_slice(&request) {
Ok(req) => req,
Err(e) => {
let response = json!({"error": e.to_string()});
let val = serde_json::to_string(&response).unwrap();
return prepare_header(val);
}
};
// TODO: Forward this information to rdsys
for bridge_str in blocked_bridges.keys() {
let bridge = array_bytes::hex2array(bridge_str).unwrap();
if self.tp_bridge_infos.lock().unwrap().contains_key(&bridge) {
let bl = self
.tp_bridge_infos
.lock()
.unwrap()
.get(&bridge)
.unwrap()
.bridge_line;
self.mark_blocked(bl);
}
}
prepare_header("OK".to_string())
}
// Verify one negative report, return true if verification succeeds
pub fn verify_negative_report(&self, report: NegativeReport) -> bool {
match self

View File

@ -249,7 +249,7 @@ mod tests {
to_be_replaced_bridges: Arc::new(Mutex::new(Vec::new())),
tp_bridge_infos: Arc::new(Mutex::new(std::collections::HashMap::<
[u8; 20],
troll_patrol::bridge_verification_info::BridgeVerificationInfo,
troll_patrol::bridge_info::BridgeInfo,
>::new())),
metrics: Metrics::default(),
};

View File

@ -2,15 +2,7 @@ use chrono::{Duration, Utc};
use lox_library::bridge_table::{BridgeLine, BRIDGE_INFO_BYTES, MAX_BRIDGES_PER_BUCKET};
use rdsys_backend::proto::Resource;
/// Since the last passed time for a working > non-working resource
/// may be older than the current time by rdsys' expiry time (currently 18 hours): https://gitlab.torproject.org/tpo/anti-censorship/rdsys/-/blob/main/pkg/usecases/resources/bridges.go?ref_type=heads#L176
/// the distributor must use that time to decide on the ACCEPTED_HOURS_OF_FAILURE
pub const RDSYS_EXPIRY: i64 = 18;
/// This value must correspond with rdsys' expiry time
/// and decide on an acceptable grace period for resources that aren't working
/// but may come back (and so shouldn't be replaced)
pub const ACCEPTED_HOURS_OF_FAILURE: i64 = 3 + RDSYS_EXPIRY;
pub const ACCEPTED_HOURS_OF_FAILURE: i64 = 3;
// Parse each resource from rdsys into a Bridgeline as expected by the Lox Bridgetable and return
// Bridgelines as two vectors, those that are marked as blocked in a specified region (indicated in the config file)
@ -138,8 +130,6 @@ mod tests {
use chrono::{Duration, Utc};
use crate::resource_parser::ACCEPTED_HOURS_OF_FAILURE;
use super::sort_for_parsing;
pub fn make_resource(
@ -189,7 +179,7 @@ mod tests {
"123.456.789.100".to_owned(),
3002,
"BE84A97D02130470A1C77839954392BA979F7EE1".to_owned(),
ACCEPTED_HOURS_OF_FAILURE-1,
2,
);
let resource_two = make_resource(
"https".to_owned(),
@ -203,7 +193,7 @@ mod tests {
"123.222.333.444".to_owned(),
6002,
"C56B9EF202130470A1C77839954392BA979F7FF9".to_owned(),
ACCEPTED_HOURS_OF_FAILURE+2,
5,
);
let resource_three = make_resource(
"scramblesuit".to_owned(),
@ -217,7 +207,7 @@ mod tests {
"443.288.222.100".to_owned(),
3042,
"5E3A8BD902130470A1C77839954392BA979F7B46".to_owned(),
ACCEPTED_HOURS_OF_FAILURE+1,
4,
);
let resource_four = make_resource(
"https".to_owned(),
@ -231,7 +221,7 @@ mod tests {
"555.444.212.100".to_owned(),
8022,
"FF024DC302130470A1C77839954392BA979F7AE2".to_owned(),
ACCEPTED_HOURS_OF_FAILURE,
3,
);
let resource_five = make_resource(
"https".to_owned(),
@ -259,7 +249,7 @@ mod tests {
"434.777.212.100".to_owned(),
10112,
"7B4DE04A22130470A1C77839954392BA979F7AE2".to_owned(),
0,
1,
);
let resource_seven = make_resource(
"https".to_owned(),

View File

@ -18,10 +18,6 @@ pub async fn handle(
.body(Body::from("Allow POST"))
.unwrap()),
_ => match (req.method(), req.uri().path()) {
(&Method::POST, "/reportblocked") => Ok::<_, Infallible>({
let bytes = body::to_bytes(req.into_body()).await.unwrap();
cloned_context.report_blocked_bridges(bytes)
}),
(&Method::POST, "/verifynegative") => Ok::<_, Infallible>({
let bytes = body::to_bytes(req.into_body()).await.unwrap();
cloned_context.verify_negative_reports(bytes)
@ -53,7 +49,6 @@ mod tests {
proto::*,
scalar_u32, BridgeAuth, BridgeDb,
};
use lox_zkp::ProofError;
use rand::RngCore;
use sha1::{Digest, Sha1};
use std::{
@ -61,8 +56,8 @@ mod tests {
sync::{Arc, Mutex},
};
use troll_patrol::{
bridge_verification_info::BridgeVerificationInfo,
negative_report::{HashOfBridgeLine, HashOfBucket, NegativeReport, ProofOfBridgeKnowledge},
bridge_info::BridgeInfo,
negative_report::{NegativeReport, SerializableNegativeReport},
positive_report::{PositiveReport, SerializablePositiveReport},
BridgeDistributor,
};
@ -70,8 +65,6 @@ mod tests {
use super::*;
trait TpClient {
fn reportblocked(&self, blocked_bridges: HashMap<String, HashSet<String>>)
-> Request<Body>;
fn verifynegative(&self, reports: BTreeMap<String, u32>) -> Request<Body>;
fn verifypositive(&self, reports: Vec<SerializablePositiveReport>) -> Request<Body>;
}
@ -79,18 +72,6 @@ mod tests {
struct TpClientMock {}
impl TpClient for TpClientMock {
fn reportblocked(
&self,
blocked_bridges: HashMap<String, HashSet<String>>,
) -> Request<Body> {
let req = serde_json::to_string(&blocked_bridges).unwrap();
Request::builder()
.method("POST")
.uri("http://localhost/reportblocked")
.body(Body::from(req))
.unwrap()
}
fn verifynegative(&self, reports: BTreeMap<String, u32>) -> Request<Body> {
let req = serde_json::to_string(&reports).unwrap();
Request::builder()
@ -138,13 +119,62 @@ mod tests {
ba: Arc::new(Mutex::new(lox_auth)),
extra_bridges: Arc::new(Mutex::new(Vec::new())),
to_be_replaced_bridges: Arc::new(Mutex::new(Vec::new())),
tp_bridge_infos: Arc::new(Mutex::new(
HashMap::<[u8; 20], BridgeVerificationInfo>::new(),
)),
tp_bridge_infos: Arc::new(Mutex::new(HashMap::<[u8; 20], BridgeInfo>::new())),
metrics: Metrics::default(),
};
Self { context }
}
pub fn generate_bridge_infos(&self) {
// We want to ignore empty bridgelines
let mut hasher = Sha1::new();
hasher.update([0; 20]);
let empty_bridgeline_fingerprint: [u8; 20] = hasher.finalize().into();
let mut lox_auth = self.context.ba.lock().unwrap();
// Recompute table
let mut tp_bridge_infos = self.context.tp_bridge_infos.lock().unwrap();
tp_bridge_infos.clear();
// Go through all buckets and all bridges in buckets, map bridge to
// buckets containing it. Note that a bridge may be contained within
// multiple buckets (open invitaion buckets and invite-only buckets).
let buckets = &lox_auth.bridge_table.buckets;
for id in buckets.keys() {
let bridges = buckets.get(id).unwrap();
let key = lox_auth.bridge_table.keys.get(id).unwrap();
let bucket = bridge_table::to_scalar(*id, key);
for bridge in bridges {
// Get hashed fingerprint
let mut hasher = Sha1::new();
hasher.update(&bridge.fingerprint);
let fingerprint: [u8; 20] = hasher.finalize().into();
if fingerprint != empty_bridgeline_fingerprint {
// Add new entry or add bucket to existing entry
if tp_bridge_infos.contains_key(&fingerprint) {
tp_bridge_infos
.get_mut(&fingerprint)
.unwrap()
.buckets
.insert(bucket);
} else {
let mut buckets = HashSet::<Scalar>::new();
buckets.insert(bucket);
tp_bridge_infos.insert(
fingerprint,
BridgeInfo {
bridge_line: *bridge,
buckets: buckets,
pubkey: None, // TODO: add pubkey for signed bridge tokens
},
);
}
}
}
}
}
}
pub fn random() -> BridgeLine {
@ -184,19 +214,6 @@ mod tests {
String::from_utf8(body_bytes.to_vec()).unwrap()
}
async fn get_bucket(
th: &mut TestHarness,
cred: &Lox,
) -> [BridgeLine; bridge_table::MAX_BRIDGES_PER_BUCKET] {
let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap();
let mut ba = th.context.ba.lock().unwrap();
let encbuckets = ba.enc_bridge_table();
let bucket =
bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap())
.unwrap();
bucket.0
}
async fn get_new_credential(th: &mut TestHarness) -> Lox {
let inv = th.context.db.lock().unwrap().invite().unwrap();
let (req, state) = open_invite::request(&inv);
@ -213,7 +230,7 @@ mod tests {
cred
}
async fn level_up(th: &mut TestHarness, cred: &Lox) -> Result<Lox, ProofError> {
async fn level_up(th: &mut TestHarness, cred: &Lox) -> Lox {
let current_level = scalar_u32(&cred.trust_level).unwrap();
if current_level == 0 {
th.context
@ -221,13 +238,13 @@ mod tests {
let mut ba = th.context.ba.lock().unwrap();
let (promreq, promstate) =
trust_promotion::request(cred, &ba.lox_pub, ba.today()).unwrap();
let promresp = ba.handle_trust_promotion(promreq)?;
let migcred = trust_promotion::handle_response(promstate, promresp)?;
let promresp = ba.handle_trust_promotion(promreq).unwrap();
let migcred = trust_promotion::handle_response(promstate, promresp).unwrap();
let (migreq, migstate) =
migration::request(cred, &migcred, &ba.lox_pub, &ba.migration_pub).unwrap();
let migresp = ba.handle_migration(migreq)?;
let migresp = ba.handle_migration(migreq).unwrap();
let new_cred = migration::handle_response(migstate, migresp, &ba.lox_pub).unwrap();
Ok(new_cred)
new_cred
} else {
th.context.advance_days_test(
level_up::LEVEL_INTERVAL[usize::try_from(current_level).unwrap()]
@ -240,11 +257,7 @@ mod tests {
let bucket =
bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap())
.unwrap();
let reachcred = match bucket.1 {
Some(v) => v,
None => return Err(ProofError::VerificationFailure),
};
//let reachcred = bucket.1.unwrap();
let reachcred = bucket.1.unwrap();
let (lvreq, lvstate) = level_up::request(
cred,
&reachcred,
@ -253,116 +266,34 @@ mod tests {
ba.today(),
)
.unwrap();
let lvresp = ba.handle_level_up(lvreq)?;
let lvresp = ba.handle_level_up(lvreq).unwrap();
let new_cred = level_up::handle_response(lvstate, lvresp, &ba.lox_pub).unwrap();
Ok(new_cred)
new_cred
}
}
#[tokio::test]
async fn test_report_blocked_bridges() {
let mut th = TestHarness::new();
let tpc = TpClientMock {};
let mut Htables = HashMap::<u32, RistrettoBasepointTable>::new();
// helper function to create map of bridges from bucket to mark blocked
fn bridges_to_block(
bucket: [BridgeLine; bridge_table::MAX_BRIDGES_PER_BUCKET],
num_bridges_to_block: usize,
) -> HashMap<String, HashSet<String>> {
let mut blocked_bridges = HashMap::<String, HashSet<String>>::new();
for i in 0..num_bridges_to_block {
let mut hasher = Sha1::new();
hasher.update(bucket[i].fingerprint);
let mut countries = HashSet::<String>::new();
countries.insert("RU".to_string());
blocked_bridges.insert(array_bytes::bytes2hex("", hasher.finalize()), countries);
}
blocked_bridges
}
// Get new level 0 credential
let cred = get_new_credential(&mut th).await;
th.context.generate_tp_bridge_infos();
let bridges = get_bucket(&mut th, &cred).await;
// Block our first (and only) bridge
let blocked_bridges = bridges_to_block(bridges, 1);
let request = tpc.reportblocked(blocked_bridges);
let response = handle(th.context.clone(), &mut Htables, request)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let resp_str = body_to_string(response).await;
assert_eq!(resp_str, "OK");
th.context.generate_tp_bridge_infos();
// We should not be able to migrate to level 1
assert!(level_up(&mut th, &cred).await.is_err());
// Get new level 1 credential
let cred = get_new_credential(&mut th).await;
let cred = level_up(&mut th, &cred).await.unwrap();
th.context.generate_tp_bridge_infos();
let bridges = get_bucket(&mut th, &cred).await;
// Block as many bridges as possible without preventing level up
let blocked_bridges = bridges_to_block(
bridges,
bridge_table::MAX_BRIDGES_PER_BUCKET - bridge_table::MIN_BUCKET_REACHABILITY,
);
let request = tpc.reportblocked(blocked_bridges);
let response = handle(th.context.clone(), &mut Htables, request)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let resp_str = body_to_string(response).await;
assert_eq!(resp_str, "OK");
th.context.generate_tp_bridge_infos();
// We should still be able to level up
let cred = level_up(&mut th, &cred).await.unwrap();
th.context.generate_tp_bridge_infos();
let bridges = get_bucket(&mut th, &cred).await;
// Block enough bridges to prevent level up
let blocked_bridges = bridges_to_block(
bridges,
bridge_table::MAX_BRIDGES_PER_BUCKET - bridge_table::MIN_BUCKET_REACHABILITY + 1,
);
let request = tpc.reportblocked(blocked_bridges);
let response = handle(th.context.clone(), &mut Htables, request)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let resp_str = body_to_string(response).await;
assert_eq!(resp_str, "OK");
// We should not be able to level up
assert!(level_up(&mut th, &cred).await.is_err());
}
#[tokio::test]
async fn test_negative_reports() {
let mut th = TestHarness::new();
th.generate_bridge_infos();
let tpc = TpClientMock {};
let mut Htables = HashMap::<u32, RistrettoBasepointTable>::new();
// Get new level 1 credential
let cred = get_new_credential(&mut th).await;
let cred = level_up(&mut th, &cred).await.unwrap();
let cred = level_up(&mut th, &cred).await;
th.context.generate_tp_bridge_infos();
th.generate_bridge_infos();
let bridges = get_bucket(&mut th, &cred).await;
let mut ba = th.context.ba.lock().unwrap();
// Get bucket
let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap();
let encbuckets = ba.enc_bridge_table();
let bucket =
bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap())
.unwrap();
let bridges = bucket.0;
// Create random number of negative reports for each bridge in bucket
let mut rng = rand::thread_rng();
@ -373,74 +304,33 @@ mod tests {
let report_1 =
NegativeReport::from_bridgeline(bridges[0], "ru".to_string(), BridgeDistributor::Lox);
println!(
"report_1: {}, count: {}",
array_bytes::bytes2hex("", report_1.fingerprint),
num_report_1
);
reports.insert(report_1.to_json(), num_report_1);
let report_2 =
NegativeReport::from_lox_bucket(bridges[1].fingerprint, cred.bucket, "ru".to_string());
println!(
"report_2: {}, count: {}",
array_bytes::bytes2hex("", report_2.fingerprint),
num_report_2
);
reports.insert(report_2.to_json(), num_report_2);
let report_3 =
NegativeReport::from_lox_credential(bridges[2].fingerprint, cred, "ru".to_string());
println!(
"report_3: {}, count: {}",
array_bytes::bytes2hex("", report_3.fingerprint),
num_report_3
);
reports.insert(report_3.to_json(), num_report_3);
// Check that reports with invalid fields are not counted
let num_invalid_report_1 = rng.next_u32() % 4 + 1;
let num_invalid_report_2 = rng.next_u32() % 4 + 1;
// Date in the future
let mut invalid_report_1 =
NegativeReport::from_bridgeline(bridges[0], "ru".to_string(), BridgeDistributor::Lox)
.to_serializable_report();
invalid_report_1.date = invalid_report_1.date + 2;
reports.insert(
serde_json::to_string(&invalid_report_1).unwrap(),
num_invalid_report_1,
);
// Invalid country code
let invalid_report_2 =
NegativeReport::from_bridgeline(bridges[1], "xx".to_string(), BridgeDistributor::Lox)
.to_serializable_report();
reports.insert(
serde_json::to_string(&invalid_report_2).unwrap(),
num_invalid_report_2,
);
// Check that well-formed reports with incorrect bridge data are not counted
let num_invalid_report_3 = rng.next_u32() % 4 + 1;
let num_invalid_report_4 = rng.next_u32() % 4 + 1;
let num_invalid_report_5 = rng.next_u32() % 4 + 1;
let mut hasher = Sha1::new();
hasher.update([0; 20]);
let empty_bridgeline_fingerprint: [u8; 20] = hasher.finalize().into();
// Unknown bridge fingerprint
let mut invalid_report_3 =
NegativeReport::from_bridgeline(bridges[2], "ru".to_string(), BridgeDistributor::Lox);
invalid_report_3.fingerprint = empty_bridgeline_fingerprint;
reports.insert(invalid_report_3.to_json(), num_invalid_report_3);
// Incorrect BridgeLine hash
let invalid_report_4 = NegativeReport::new(
bridges[0].fingerprint,
ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(&BridgeLine::default())),
"ru".to_string(),
BridgeDistributor::Lox,
);
reports.insert(invalid_report_4.to_json(), num_invalid_report_4);
// Incorrect bucket hash
let invalid_report_5 = NegativeReport::new(
bridges[1].fingerprint,
ProofOfBridgeKnowledge::HashOfBucket(HashOfBucket::new(&Scalar::ZERO)),
"ru".to_string(),
BridgeDistributor::Lox,
);
reports.insert(invalid_report_5.to_json(), num_invalid_report_5);
// Ensure each negative report is distinct and added successfully
assert_eq!(reports.keys().len(), 8);
// TODO: Check reports with invalid fields
// TODO: Check well-formed reports with incorrect bridge data
let request = tpc.verifynegative(reports);
let response = handle(th.context.clone(), &mut Htables, request)
@ -450,83 +340,4 @@ mod tests {
let count: u32 = body_to_string(response).await.parse().unwrap();
assert_eq!(num_report_1 + num_report_2 + num_report_3, count);
}
#[tokio::test]
async fn test_positive_reports() {
let mut th = TestHarness::new();
let tpc = TpClientMock {};
let mut Htables = HashMap::<u32, RistrettoBasepointTable>::new();
// Get new level 3 credential
let cred = get_new_credential(&mut th).await;
let cred = level_up(&mut th, &cred).await.unwrap();
let cred = level_up(&mut th, &cred).await.unwrap();
let cred = level_up(&mut th, &cred).await.unwrap();
th.context.generate_tp_bridge_infos();
let bridges = get_bucket(&mut th, &cred).await;
// Create a positive report for each bridge in bucket
let mut reports = Vec::<SerializablePositiveReport>::new();
for bridge in bridges {
let report = PositiveReport::from_lox_credential(
bridge.fingerprint,
None,
&cred,
&th.context.ba.lock().unwrap().lox_pub,
"ru".to_string(),
);
reports.push(report.to_serializable_report());
}
assert_eq!(reports.len(), 3);
// Check that reports with invalid fields are not counted
// Date in the future
let mut invalid_report_1 = PositiveReport::from_lox_credential(
bridges[0].fingerprint,
None,
&cred,
&th.context.ba.lock().unwrap().lox_pub,
"ru".to_string(),
);
invalid_report_1.date = invalid_report_1.date + 2;
reports.push(invalid_report_1.to_serializable_report());
// Invalid country code
let invalid_report_2 = PositiveReport::from_lox_credential(
bridges[1].fingerprint,
None,
&cred,
&th.context.ba.lock().unwrap().lox_pub,
"xx".to_string(),
);
reports.push(invalid_report_2.to_serializable_report());
// Check that well-formed reports with incorrect bridge data are not counted
let mut hasher = Sha1::new();
hasher.update([0; 20]);
let empty_bridgeline_fingerprint: [u8; 20] = hasher.finalize().into();
// Unknown bridge fingerprint
let mut invalid_report_3 = PositiveReport::from_lox_credential(
bridges[2].fingerprint,
None,
&cred,
&th.context.ba.lock().unwrap().lox_pub,
"ru".to_string(),
);
invalid_report_3.fingerprint = empty_bridgeline_fingerprint;
reports.push(invalid_report_3.to_serializable_report());
let request = tpc.verifypositive(reports);
let response = handle(th.context.clone(), &mut Htables, request)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let count: usize = body_to_string(response).await.parse().unwrap();
assert_eq!(count, lox_library::bridge_table::MAX_BRIDGES_PER_BUCKET);
}
}

View File

@ -19,18 +19,18 @@ ed25519-dalek = { version = "2", features = ["serde", "rand_core"] }
bincode = "1"
chrono = "0.4"
rand = { version = "0.8", features = ["std_rng"]}
serde = "1.0.197"
serde_with = {version = "3.7.0", features = ["json"]}
serde = "1.0.195"
serde_with = {version = "3.5.0", features = ["json"]}
sha2 = "0.10"
statistical = "1.0.0"
lazy_static = "1"
hex_fmt = "0.3"
aes-gcm = { version = "0.10", features =["aes"]}
base64 = "0.21"
time = "0.3.34"
time = "0.3.31"
prometheus = "0.13.3"
subtle = "2.5"
thiserror = "1.0.57"
thiserror = "1.0.56"
lox-zkp = { git = "https://gitlab.torproject.org/onyinyang/lox-zkp", version = "0.8.0" }
[features]

View File

@ -94,12 +94,6 @@ pub enum OpenInvitationError {
NoBridgesAvailable,
}
#[derive(Error, Debug)]
pub enum BridgeTableError {
#[error("The bucket corresponding to key {0} was not in the bridge table")]
MissingBucket(u32),
}
/// Private Key of the Issuer
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct IssuerPrivKey {
@ -162,7 +156,6 @@ pub struct BridgeDb {
pub pubkey: VerifyingKey,
/// The set of open-invitation buckets
openinv_buckets: HashSet<u32>,
/// The set of open invitation buckets that have been distributed
distributed_buckets: Vec<u32>,
#[serde(skip)]
today: DateTime<Utc>,
@ -236,14 +229,14 @@ impl BridgeDb {
self.today = Utc::now();
self.daily_bridges_distributed = 0;
}
if self.openinv_buckets.is_empty() {
return Err(OpenInvitationError::NoBridgesAvailable);
}
if self.daily_bridges_distributed < MAX_DAILY_BRIDGES {
if self.current_k < OPENINV_K && !self.distributed_buckets.is_empty() {
bucket_num = *self.distributed_buckets.last().unwrap();
self.current_k += 1;
} else {
if self.openinv_buckets.is_empty() {
return Err(OpenInvitationError::NoBridgesAvailable);
}
// Choose a random bucket number (from the set of open
// invitation buckets) and serialize it
let openinv_vec: Vec<&u32> = self.openinv_buckets.iter().collect();
@ -516,82 +509,121 @@ impl BridgeAuth {
res
}
// Repurpose a bucket of spares into unallocated bridges
pub fn dissolve_spare_bucket(&mut self, key: u32) -> Result<(), BridgeTableError> {
self.bridge_table.spares.remove(&key);
// Get the actual bridges from the spare bucket
let spare_bucket = self
.bridge_table
.buckets
.remove(&key)
.ok_or(BridgeTableError::MissingBucket(key))?;
for bridge in spare_bucket.iter() {
self.bridge_table.unallocated_bridges.push(*bridge);
// Mark bucket as unreachable while it is unallocated
self.bridge_table.reachable.remove(bridge);
}
self.bridge_table.keys.remove(&key);
self.bridge_table.recycleable_keys.push(key);
Ok(())
}
/// Attempt to remove a bridge that is failing tests and replace it with a bridge from
/// available_bridge or from a spare bucket
pub fn bridge_replace(
&mut self,
bridge: &BridgeLine,
available_bridge: Option<BridgeLine>,
available_bridge: Option<&BridgeLine>,
) -> ReplaceSuccess {
let mut res = ReplaceSuccess::NotFound;
let reachable_bridges = &self.bridge_table.reachable.clone();
let Some(positions) = reachable_bridges.get(bridge) else {
return ReplaceSuccess::NotFound;
match reachable_bridges.get(bridge) {
Some(positions) => {
if let Some(replacement) = available_bridge {
for (bucketnum, offset) in positions.iter() {
let mut bridgelines = match self.bridge_table.buckets.get(bucketnum) {
Some(bridgelines) => *bridgelines,
None => return ReplaceSuccess::NotFound,
};
assert!(bridgelines[*offset] == *bridge);
bridgelines[*offset] = *replacement;
self.bridge_table.buckets.insert(*bucketnum, bridgelines);
// Remove the bridge from the reachable bridges and add new bridge
self.bridge_table
.reachable
.insert(*replacement, positions.clone());
// Remove the bridge from the bucket
self.bridge_table.reachable.remove(bridge);
}
res = ReplaceSuccess::Replaced
} else if !self.bridge_table.unallocated_bridges.is_empty() {
let replacement = &self.bridge_table.unallocated_bridges.pop().unwrap();
for (bucketnum, offset) in positions.iter() {
let mut bridgelines = match self.bridge_table.buckets.get(bucketnum) {
Some(bridgelines) => *bridgelines,
// This should not happen if the rest of the function is correct, we can assume unwrap will succeed
None => return ReplaceSuccess::NotReplaced,
};
assert!(bridgelines[*offset] == *bridge);
bridgelines[*offset] = *replacement;
self.bridge_table.buckets.insert(*bucketnum, bridgelines);
self.bridge_table
.reachable
.insert(*replacement, positions.clone());
// Remove the bridge from the bucket
self.bridge_table.reachable.remove(bridge);
}
res = ReplaceSuccess::Replaced
} else if !self.bridge_table.spares.is_empty() {
// First get the bucketnums for the replacement bridge in case it is a spare
let mut bucketnums: Vec<u32> = Vec::new();
for (bucketnum, _) in positions.iter() {
bucketnums.push(*bucketnum);
}
// Get the first spare and remove it from the spares set.
let mut spare = *self.bridge_table.spares.iter().next().unwrap();
// Check that the first spare in the list of spares is not the one to be replaced
if bucketnums.contains(&spare) {
// If it is, take the last spare instead
spare = *self.bridge_table.spares.iter().last().unwrap();
// If this is the same bucketnum, there is only one spare bucket with the bridge
// to be replaced in it, so don't replace it.
if bucketnums.contains(&spare) {
res = ReplaceSuccess::NotReplaced;
return res;
}
}
self.bridge_table.spares.remove(&spare);
self.bridge_table.recycleable_keys.push(spare);
// Get the actual bridges from the spare bucket
let spare_bucket = match self.bridge_table.buckets.remove(&spare) {
Some(spare_bucket) => spare_bucket,
// This should not happen if the rest of the functions are correct, we can assume unwrap will succeed
None => return ReplaceSuccess::NotReplaced,
};
// Remove the spare bucket uid from the keys map
self.bridge_table.keys.remove(&spare);
let mut replacement: &BridgeLine = &BridgeLine::default();
// Make the first spare the replacement bridge, add the others to the set of
// unallocated_bridges
for spare_bridge in spare_bucket.iter() {
if replacement.port > 0 {
self.bridge_table.unallocated_bridges.push(*spare_bridge);
// Mark bucket as unreachable while it is unallocated
self.bridge_table.reachable.remove(spare_bridge);
} else {
replacement = spare_bridge;
}
}
for (bucketnum, offset) in positions.iter() {
let mut bridgelines = match self.bridge_table.buckets.get(bucketnum) {
Some(bridgelines) => *bridgelines,
None => return ReplaceSuccess::NotReplaced,
};
assert!(bridgelines[*offset] == *bridge);
bridgelines[*offset] = *replacement;
self.bridge_table.buckets.insert(*bucketnum, bridgelines);
self.bridge_table
.reachable
.insert(*replacement, positions.clone());
// Remove the bridge from the bucket
self.bridge_table.reachable.remove(bridge);
}
res = ReplaceSuccess::Replaced
} else {
println!("No available bridges");
// If there are no available bridges that can be assigned here, the only thing
// that can be done is return an indication that updating the gone bridge
// didn't work.
// In this case, we do not mark the bridge as unreachable or remove it from the
// reachable bridges so that we can still find it when a new bridge does become available
res = ReplaceSuccess::NotReplaced
}
}
None => return res,
};
// select replacement:
// - first try the given bridge
// - second try to pick one from the set of available bridges
// - third dissolve a spare bucket to create more available bridges
let Some(replacement) = available_bridge.or_else(|| {
self.bridge_table.unallocated_bridges.pop().or_else(|| {
let Some(spare) = self
.bridge_table
.spares
.iter()
// in case bridge is a spare, avoid replacing it with itself
.find(|x| !positions.iter().any(|(bucketnum, _)| &bucketnum == x))
.cloned()
else {
return None;
};
let Ok(_) = self.dissolve_spare_bucket(spare) else {
return None;
};
self.bridge_table.unallocated_bridges.pop()
})
}) else {
// If there are no available bridges that can be assigned here, the only thing
// that can be done is return an indication that updating the gone bridge
// didn't work.
// In this case, we do not mark the bridge as unreachable or remove it from the
// reachable bridges so that we can still find it when a new bridge does become available
println!("No available bridges");
return ReplaceSuccess::NotReplaced;
};
for (bucketnum, offset) in positions.iter() {
let mut bridgelines = match self.bridge_table.buckets.get(bucketnum) {
Some(bridgelines) => *bridgelines,
None => return ReplaceSuccess::NotFound,
};
assert!(bridgelines[*offset] == *bridge);
bridgelines[*offset] = replacement;
self.bridge_table.buckets.insert(*bucketnum, bridgelines);
// Remove the bridge from the reachable bridges and add new bridge
self.bridge_table
.reachable
.insert(replacement, positions.clone());
// Remove the bridge from the bucket
self.bridge_table.reachable.remove(bridge);
}
ReplaceSuccess::Replaced
res
}
/// Mark a bridge as blocked
@ -980,7 +1012,6 @@ pub fn pt_dbl(P: &RistrettoPoint) -> RistrettoPoint {
pub mod proto {
pub mod blockage_migration;
pub mod check_blockage;
pub mod errors;
pub mod issue_invite;
pub mod level_up;
pub mod migration;

View File

@ -52,8 +52,6 @@ use super::super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE};
use super::check_blockage::MIN_TRUST_LEVEL;
use super::level_up::LEVEL_INVITATIONS;
use super::errors::CredentialError;
#[derive(Serialize, Deserialize)]
pub struct Request {
// Fields for blind showing the Lox credential
@ -184,7 +182,7 @@ pub fn request(
migration_cred: &cred::Migration,
lox_pub: &IssuerPubKey,
migration_pub: &IssuerPubKey,
) -> Result<(Request, State), CredentialError> {
) -> Result<(Request, State), ProofError> {
let A: &RistrettoPoint = &CMZ_A;
let B: &RistrettoPoint = &CMZ_B;
let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
@ -194,24 +192,16 @@ pub fn request(
// ids match and the Lox credential bucket matches the Migration
// credential from_bucket
if lox_cred.id != migration_cred.lox_id || lox_cred.bucket != migration_cred.from_bucket {
return Err(CredentialError::CredentialMismatch);
return Err(ProofError::VerificationFailure);
}
// The trust level must be at least MIN_TRUST_LEVEL
let level: u32 = match scalar_u32(&lox_cred.trust_level) {
Some(v) => v,
None => {
return Err(CredentialError::InvalidField(
String::from("trust_level"),
String::from("could not be converted to u32"),
))
}
None => return Err(ProofError::VerificationFailure),
};
if level < MIN_TRUST_LEVEL {
return Err(CredentialError::InvalidField(
String::from("trust_level"),
format!("level {:?} not in range", level),
));
return Err(ProofError::VerificationFailure);
}
// Blind showing the Lox credential

View File

@ -49,8 +49,6 @@ use super::super::scalar_u32;
use super::super::{BridgeAuth, IssuerPubKey};
use super::super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE};
use super::errors::CredentialError;
/// The minimum trust level a Lox credential must have to be allowed to
/// perform this protocol.
pub const MIN_TRUST_LEVEL: u32 = 3;
@ -124,7 +122,7 @@ define_proof! {
pub fn request(
lox_cred: &cred::Lox,
lox_pub: &IssuerPubKey,
) -> Result<(Request, State), CredentialError> {
) -> Result<(Request, State), ProofError> {
let A: &RistrettoPoint = &CMZ_A;
let B: &RistrettoPoint = &CMZ_B;
let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
@ -134,18 +132,10 @@ pub fn request(
// that trust_level >= MIN_TRUST_LEVEL
let level: u32 = match scalar_u32(&lox_cred.trust_level) {
Some(v) => v,
None => {
return Err(CredentialError::InvalidField(
String::from("trust_level"),
String::from("could not be converted to u32"),
))
}
None => return Err(ProofError::VerificationFailure),
};
if level < MIN_TRUST_LEVEL {
return Err(CredentialError::InvalidField(
String::from("trust_level"),
format!("level {:?} not in range", level),
));
return Err(ProofError::VerificationFailure);
}
// Blind showing the Lox credential

View File

@ -1,19 +0,0 @@
use thiserror::Error;
/// This error is thrown if the number of buckets/keys in the bridge table
/// exceeds u32 MAX.It is unlikely this error will ever occur.
#[derive(Error, Debug)]
pub enum CredentialError {
#[error("time threshold for operation will not be met for {0} more days")]
TimeThresholdNotMet(u32),
#[error("credential has expired")]
CredentialExpired,
#[error("invalid field {0}: {1}")]
InvalidField(String, String),
#[error("exceeded blockages threshold")]
ExceededBlockagesThreshold,
#[error("credential has no available invitations")]
NoInvitationsRemaining,
#[error("supplied credentials do not match")]
CredentialMismatch,
}

View File

@ -62,8 +62,6 @@ use super::super::scalar_u32;
use super::super::{BridgeAuth, IssuerPubKey};
use super::super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE};
use super::errors::CredentialError;
#[derive(Serialize, Deserialize)]
pub struct Request {
// Fields for blind showing the Lox credential
@ -263,7 +261,7 @@ pub fn request(
lox_pub: &IssuerPubKey,
reach_pub: &IssuerPubKey,
today: u32,
) -> Result<(Request, State), CredentialError> {
) -> Result<(Request, State), ProofError> {
let A: &RistrettoPoint = &CMZ_A;
let B: &RistrettoPoint = &CMZ_B;
let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
@ -272,28 +270,20 @@ pub fn request(
// Ensure the credential can be correctly shown: it must be the case
// that invites_remaining not be 0
if lox_cred.invites_remaining == Scalar::ZERO {
return Err(CredentialError::NoInvitationsRemaining);
return Err(ProofError::VerificationFailure);
}
// The buckets in the Lox and Bucket Reachability credentials have
// to match
if lox_cred.bucket != reach_cred.bucket {
return Err(CredentialError::CredentialMismatch);
return Err(ProofError::VerificationFailure);
}
// The Bucket Reachability credential has to be dated today
let reach_date: u32 = match scalar_u32(&reach_cred.date) {
Some(v) => v,
None => {
return Err(CredentialError::InvalidField(
String::from("date"),
String::from("could not be converted to u32"),
))
}
None => return Err(ProofError::VerificationFailure),
};
if reach_date != today {
return Err(CredentialError::InvalidField(
String::from("date"),
String::from("reachability credential must be generated today"),
));
return Err(ProofError::VerificationFailure);
}
// The new invites_remaining
let new_invites_remaining = lox_cred.invites_remaining - Scalar::ONE;

View File

@ -55,8 +55,6 @@ use super::super::{pt_dbl, scalar_dbl, scalar_u32};
use super::super::{BridgeAuth, IssuerPubKey};
use super::super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE};
use super::errors::CredentialError;
/// The maximum trust level in the system. A user can run this level
/// upgrade protocol when they're already at the max level; they will
/// get a fresh invites_remaining batch, and reset their level_since
@ -274,7 +272,7 @@ pub fn request(
lox_pub: &IssuerPubKey,
reach_pub: &IssuerPubKey,
today: u32,
) -> Result<(Request, State), CredentialError> {
) -> Result<(Request, State), ProofError> {
let A: &RistrettoPoint = &CMZ_A;
let B: &RistrettoPoint = &CMZ_B;
let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
@ -284,83 +282,50 @@ pub fn request(
// that level_since + LEVEL_INTERVAL[level] <= today.
let level_since: u32 = match scalar_u32(&lox_cred.level_since) {
Some(v) => v,
None => {
return Err(CredentialError::InvalidField(
String::from("level_since"),
String::from("could not be converted to u32"),
))
}
None => return Err(ProofError::VerificationFailure),
};
// The trust level has to be at least 1
let trust_level: u32 = match scalar_u32(&lox_cred.trust_level) {
Some(v) => v,
None => {
return Err(CredentialError::InvalidField(
String::from("trust_level"),
String::from("could not be converted to u32"),
))
}
None => return Err(ProofError::VerificationFailure),
};
if trust_level < 1 || (trust_level as usize) > MAX_LEVEL {
return Err(CredentialError::InvalidField(
String::from("trust_level"),
format!("level {:?} not in range", trust_level),
));
return Err(ProofError::VerificationFailure);
}
// The trust level has to be no higher than the highest level
let level_interval: u32 = match LEVEL_INTERVAL.get(trust_level as usize) {
Some(&v) => v,
None => {
return Err(CredentialError::InvalidField(
String::from("trust_level"),
format!("level {:?} not in range", trust_level),
))
}
None => return Err(ProofError::VerificationFailure),
};
if level_since + level_interval > today {
return Err(CredentialError::TimeThresholdNotMet(
level_since + level_interval - today,
));
return Err(ProofError::VerificationFailure);
}
// The credential can't be _too_ old
let diffdays = today - (level_since + level_interval);
if diffdays > 511 {
return Err(CredentialError::CredentialExpired);
return Err(ProofError::VerificationFailure);
}
// The current number of blockages
let blockages: u32 = match scalar_u32(&lox_cred.blockages) {
Some(v) => v,
None => {
return Err(CredentialError::InvalidField(
String::from("blockages"),
String::from("could not be converted to u32"),
))
}
None => return Err(ProofError::VerificationFailure),
};
if blockages > MAX_BLOCKAGES[trust_level as usize] {
return Err(CredentialError::ExceededBlockagesThreshold);
return Err(ProofError::VerificationFailure);
}
let blockage_diff = MAX_BLOCKAGES[trust_level as usize] - blockages;
// The buckets in the Lox and Bucket Reachability credentials have
// to match
if lox_cred.bucket != reach_cred.bucket {
return Err(CredentialError::CredentialMismatch);
return Err(ProofError::VerificationFailure);
}
// The Bucket Reachability credential has to be dated today
let reach_date: u32 = match scalar_u32(&reach_cred.date) {
Some(v) => v,
None => {
return Err(CredentialError::InvalidField(
String::from("date"),
String::from("could not be converted to u32"),
))
}
None => return Err(ProofError::VerificationFailure),
};
if reach_date != today {
return Err(CredentialError::InvalidField(
String::from("date"),
String::from("reachability credential must be generated today"),
));
return Err(ProofError::VerificationFailure);
}
// The new trust level
let new_level = if (trust_level as usize) < MAX_LEVEL {

View File

@ -46,8 +46,6 @@ use super::super::dup_filter::SeenType;
use super::super::{BridgeAuth, IssuerPubKey};
use super::super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE};
use super::errors::CredentialError;
#[derive(Serialize, Deserialize)]
pub struct Request {
// Fields for blind showing the Lox credential
@ -155,7 +153,7 @@ pub fn request(
migration_cred: &cred::Migration,
lox_pub: &IssuerPubKey,
migration_pub: &IssuerPubKey,
) -> Result<(Request, State), CredentialError> {
) -> Result<(Request, State), ProofError> {
let A: &RistrettoPoint = &CMZ_A;
let B: &RistrettoPoint = &CMZ_B;
let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
@ -165,16 +163,13 @@ pub fn request(
// ids match and the Lox credential bucket matches the Migration
// credential from_bucket
if lox_cred.id != migration_cred.lox_id || lox_cred.bucket != migration_cred.from_bucket {
return Err(CredentialError::CredentialMismatch);
return Err(ProofError::VerificationFailure);
}
// This protocol only allows migrating from trust level 0 to trust
// level 1
if lox_cred.trust_level != Scalar::ZERO {
return Err(CredentialError::InvalidField(
String::from("trust_level"),
String::from("must be zero"),
));
return Err(ProofError::VerificationFailure);
}
// Blind showing the Lox credential

View File

@ -38,8 +38,6 @@ use super::super::{pt_dbl, scalar_dbl, scalar_u32};
use super::super::{BridgeAuth, IssuerPubKey};
use super::super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE};
use super::errors::CredentialError;
/// Invitations must be used within this many days of being issued.
/// Note that if you change this number to be larger than 15, you must
/// also add bits to the zero knowledge proof.
@ -176,7 +174,7 @@ pub fn request(
inv_cred: &cred::Invitation,
invitation_pub: &IssuerPubKey,
today: u32,
) -> Result<(Request, State), CredentialError> {
) -> Result<(Request, State), ProofError> {
let A: &RistrettoPoint = &CMZ_A;
let B: &RistrettoPoint = &CMZ_B;
let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
@ -186,24 +184,16 @@ pub fn request(
// that date + INVITATION_EXPIRY >= today.
let date: u32 = match scalar_u32(&inv_cred.date) {
Some(v) => v,
None => {
return Err(CredentialError::InvalidField(
String::from("date"),
String::from("could not be converted to u32"),
))
}
None => return Err(ProofError::VerificationFailure),
};
if date + INVITATION_EXPIRY < today {
return Err(CredentialError::CredentialExpired);
return Err(ProofError::VerificationFailure);
}
let diffdays = date + INVITATION_EXPIRY - today;
// If diffdays > 15, then since INVITATION_EXPIRY <= 15, then date
// must be in the future. Reject.
if diffdays > 15 {
return Err(CredentialError::InvalidField(
String::from("date"),
String::from("credential was created in the future"),
));
return Err(ProofError::VerificationFailure);
}
// Blind showing the Invitation credential

View File

@ -48,8 +48,6 @@ use super::super::{pt_dbl, scalar_dbl, scalar_u32};
use super::super::{BridgeAuth, IssuerPubKey};
use super::super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE};
use super::errors::CredentialError;
/// The minimum number of days a user has to be at trust level 0
/// (untrusted) with their (single) bridge unblocked before they can
/// move to level 1.
@ -166,7 +164,7 @@ pub fn request(
lox_cred: &cred::Lox,
lox_pub: &IssuerPubKey,
today: u32,
) -> Result<(Request, State), CredentialError> {
) -> Result<(Request, State), ProofError> {
let A: &RistrettoPoint = &CMZ_A;
let B: &RistrettoPoint = &CMZ_B;
let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
@ -176,21 +174,14 @@ pub fn request(
// that level_since + UNTRUSTED_INTERVAL <= today.
let level_since: u32 = match scalar_u32(&lox_cred.level_since) {
Some(v) => v,
None => {
return Err(CredentialError::InvalidField(
String::from("level_since"),
String::from("could not be converted to u32"),
))
}
None => return Err(ProofError::VerificationFailure),
};
if level_since + UNTRUSTED_INTERVAL > today {
return Err(CredentialError::TimeThresholdNotMet(
level_since + UNTRUSTED_INTERVAL - today,
));
return Err(ProofError::VerificationFailure);
}
let diffdays = today - (level_since + UNTRUSTED_INTERVAL);
if diffdays > 511 {
return Err(CredentialError::CredentialExpired);
return Err(ProofError::VerificationFailure);
}
// Blind showing the Lox credential

View File

@ -1091,21 +1091,21 @@ fn test_bridge_replace() {
match case {
"not found" => {
// Case zero: bridge to be replaced is not in the bridgetable
let random_bridgeline = BridgeLine::random();
let random_bridgeline = &BridgeLine::random();
assert!(
!th.ba.bridge_table.reachable.contains_key(&random_bridgeline),
!th.ba.bridge_table.reachable.contains_key(random_bridgeline),
"Random bridgeline happens to be in the bridge_table (and should not be)"
);
assert!(
th.ba
.bridge_replace(&random_bridgeline, Some(random_bridgeline))
.bridge_replace(random_bridgeline, Some(random_bridgeline))
== ReplaceSuccess::NotFound,
"Bridge should be marked as NotFound"
);
}
"available" => {
// Case one: available_bridge != null
let random_bridgeline = BridgeLine::random();
let random_bridgeline = &BridgeLine::random();
let unallocated_bridgeline = &BridgeLine::random();
th.ba
.bridge_table
@ -1115,7 +1115,7 @@ fn test_bridge_replace() {
th.ba
.bridge_table
.reachable
.get(&random_bridgeline)
.get(random_bridgeline)
.is_none(),
"Random bridge already in table"
);
@ -1129,7 +1129,7 @@ fn test_bridge_replace() {
th.ba
.bridge_table
.reachable
.get(&random_bridgeline)
.get(random_bridgeline)
.is_some(),
"Replacement bridge not added to reachable bridges"
);

View File

@ -12,11 +12,10 @@ categories = ["rust-patterns"]
repository = "https://gitlab.torproject.org/tpo/anti-censorship/lox.git/"
[dependencies]
chrono = { version = "0.4.34", features = ["serde", "clock"] }
lox-library = {path = "../lox-library", version = "0.1.0"}
serde = "1"
serde_json = "1.0.113"
serde_with = "3.7.0"
serde_json = "1.0.108"
serde_with = "3.5.0"
[features]

View File

@ -1,4 +1,3 @@
use chrono::{DateTime, Utc};
use lox_library::bridge_table::{
from_scalar, BridgeLine, BridgeTable, EncryptedBucket, MAX_BRIDGES_PER_BUCKET,
};
@ -93,14 +92,6 @@ pub const LOX_SYSTEM_INFO: LoxSystemInfo = LoxSystemInfo {
min_blockage_migration_trust_level: check_blockage::MIN_TRUST_LEVEL,
};
#[derive(Debug, Deserialize, Serialize)]
pub struct LoxNextUnlock {
pub trust_level_unlock_date: DateTime<Utc>,
pub invitation_unlock_date: DateTime<Utc>,
pub num_invitations_unlocked: u32,
pub blockage_migration_unlock_date: DateTime<Utc>,
}
#[serde_as]
#[derive(Serialize, Deserialize)]
pub struct EncBridgeTable {

View File

@ -21,14 +21,14 @@ lazy_static = "1.4.0"
lox-library = { path = "../lox-library", version = "0.1.0" }
lox_utils = { path = "../lox-utils", version = "0.1.0" }
wasm-bindgen = "0.2"
time = "0.3.34"
serde_json = "1.0.113"
time = "0.3.31"
serde_json = "1.0.108"
console_error_panic_hook = "0.1.7"
js-sys = "0.3.69"
js-sys = "0.3.66"
rand = { version = "0.7", features = ["wasm-bindgen"] }
lox-zkp = { git = "https://gitlab.torproject.org/onyinyang/lox-zkp", version = "0.8.0" }
[dependencies.chrono]
version = "0.4.34"
version = "0.4.31"
features = ["serde", "wasmbind"]

View File

@ -16,7 +16,7 @@ import init, {
handle_check_blockage,
blockage_migration,
handle_blockage_migration,
set_panic_hook, get_last_upgrade_time, get_trust_level, get_invites_remaining, get_next_unlock, get_received_invite_expiry, get_bridgelines_from_bucket} from "./pkg/lox_wasm.js";
set_panic_hook, get_last_upgrade_time, get_trust_level, get_invites_remaining, get_issued_invite_expiry, get_received_invite_expiry, get_bridgelines_from_bucket} from "./pkg/lox_wasm.js";
let pubkeys = await simple_request("/pubkeys");
console.log("Got pubkeys: " + pubkeys);
let constants = await simple_request("/constants");
@ -42,12 +42,10 @@ let open_lox_cred = await init().then(() => {
});
return cred;
});
let unlock_info = get_next_unlock(constants, open_lox_cred);
console.log("Unlock info: "+unlock_info);
get_last_upgrade_time(open_lox_cred);
get_trust_level(open_lox_cred);
get_invites_remaining(open_lox_cred);
let encrypted_table = await simple_request("/reachability");
let info_five = get_bridgelines_from_bucket(open_lox_cred, encrypted_table);
console.log("Bridgelines available: "+info_five);

View File

@ -1,4 +1,4 @@
use chrono::{DateTime, NaiveDateTime, NaiveTime, Utc};
use chrono::Utc;
use julianday::JulianDay;
use lox_library::cred::{Invitation, Migration};
use lox_library::proto::{
@ -793,92 +793,11 @@ pub fn get_bridgelines_from_bucket(
pub fn invitation_is_trusted(unspecified_invitation_str: String) -> Result<bool, JsValue> {
match serde_json::from_str::<Invitation>(&unspecified_invitation_str) {
Ok(_) => Ok(true),
Err(_) => match serde_json::from_str::<lox_utils::Invite>(&unspecified_invitation_str) {
Ok(_) => Ok(false),
Err(e) => Err(JsValue::from(e.to_string())),
},
}
}
#[wasm_bindgen]
pub fn get_next_unlock(constants_str: String, lox_cred_str: String) -> Result<String, JsValue> {
let constants: lox_utils::LoxSystemInfo = match serde_json::from_str(&constants_str) {
Ok(constants) => constants,
Err(e) => return Err(JsValue::from(e.to_string())),
};
let lox_cred: lox_utils::LoxCredential = match serde_json::from_str(&lox_cred_str) {
Ok(lox_cred) => lox_cred,
Err(e) => return Err(JsValue::from(e.to_string())),
};
let trust_level = scalar_u32(&lox_cred.lox_credential.trust_level).unwrap();
let (days_to_next_level, mut invitations_at_next_level) = match trust_level as usize {
// If the credential is at trust level 0, we use the untrusted interval from the
// trust promotion protocol to calculate the date of the next level update
0 => (constants.untrusted_interval, 0),
// Otherwise, we usethe invitation and upgrade dates from the level up protocol constants
_ => (
constants.level_interval[trust_level as usize],
constants.level_invitations[trust_level as usize],
),
};
let mut days_to_invite_inc = days_to_next_level;
// If there are no invitations at the next trust level upgrade
// i.e., if the credential is at level 0, calculate the time until they will
// unlock invitations
if invitations_at_next_level == 0 {
days_to_invite_inc =
days_to_next_level + constants.level_interval[trust_level as usize + 1];
invitations_at_next_level = constants.level_invitations[trust_level as usize + 1];
}
let days_to_blockage_migration_unlock =
match trust_level < constants.min_blockage_migration_trust_level {
// If the credential is greater than the minimum level that enables
// migrating after a blockage, the time to unlock is 0, otherwise we
// add the time to upgrade until that level
true => {
let mut blockage_days = days_to_next_level;
let mut count = 1;
while trust_level + count < constants.min_blockage_migration_trust_level {
blockage_days += constants.level_interval[trust_level as usize + count as usize];
count += 1;
}
blockage_days
Err(_) => {
match serde_json::from_str::<lox_utils::Invite>(&unspecified_invitation_str){
Ok(_) => Ok(false),
Err(e) => Err(JsValue::from(e.to_string())),
}
false => 0,
};
let day_of_level_unlock =
(scalar_u32(&lox_cred.lox_credential.level_since).unwrap() + days_to_next_level) as i32;
let level_unlock_date = JulianDay::new(day_of_level_unlock).to_date();
let day_of_invite_unlock =
(scalar_u32(&lox_cred.lox_credential.level_since).unwrap() + days_to_invite_inc) as i32;
let invite_unlock_date = JulianDay::new(day_of_invite_unlock).to_date();
let day_of_blockage_migration_unlock = (scalar_u32(&lox_cred.lox_credential.level_since).unwrap() + days_to_blockage_migration_unlock) as i32;
let blockage_migration_unlock_date =
JulianDay::new(day_of_blockage_migration_unlock as i32).to_date();
let next_unlock: lox_utils::LoxNextUnlock = lox_utils::LoxNextUnlock {
trust_level_unlock_date: DateTime::<Utc>::from_naive_utc_and_offset(
NaiveDateTime::new(level_unlock_date, NaiveTime::from_hms_opt(0, 0, 0).unwrap()),
Utc,
),
invitation_unlock_date: DateTime::<Utc>::from_naive_utc_and_offset(
NaiveDateTime::new(
invite_unlock_date,
NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
),
Utc,
),
num_invitations_unlocked: invitations_at_next_level,
blockage_migration_unlock_date: DateTime::<Utc>::from_naive_utc_and_offset(
NaiveDateTime::new(
blockage_migration_unlock_date,
NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
),
Utc,
),
};
match serde_json::to_string(&next_unlock) {
Ok(next_unlock) => Ok(next_unlock),
Err(e) => Err(JsValue::from(e.to_string())),
}
}
}

View File

@ -23,6 +23,6 @@ sha1 = "0.10.6"
tokio = { version = "1", features = ["full", "macros"] }
reqwest = { version = "0.11", features = ["json", "stream"]}
tokio-stream = "0.1.14"
futures = "0.3.30"
futures = "0.3.29"
tokio-util = "0.7.10"
chrono = { version = "0.4.34", features = ["serde", "clock"] }
chrono = { version = "0.4.31", features = ["serde", "clock"] }