├── migrations ├── .keep ├── 2022-10-19-004234_user │ ├── down.sql │ └── up.sql ├── 2022-11-17-094920_event │ ├── down.sql │ └── up.sql ├── 2022-11-17-095317_event_message │ ├── down.sql │ └── up.sql ├── 2022-10-19-225137_user_profile │ ├── down.sql │ └── up.sql ├── 2022-11-11-195501_event_subscriptions │ ├── down.sql │ └── up.sql ├── 2022-10-19-225223_user_private_settings │ ├── down.sql │ └── up.sql └── 2022-10-19-225303_user_login_passcode │ ├── down.sql │ └── up.sql ├── .env ├── rust-toolchain ├── src ├── lib.rs └── test │ └── mod.rs ├── limit-server-subs ├── src │ └── lib.rs └── Cargo.toml ├── integration_test_conf.toml ├── limit-am ├── README.md ├── Cargo.toml └── src │ └── lib.rs ├── .gitmodules ├── limit-utils ├── Cargo.toml └── src │ └── lib.rs ├── limit-config ├── Cargo.toml └── src │ └── lib.rs ├── limit-db ├── Cargo.toml └── src │ ├── macros.rs │ ├── schema.rs │ ├── event.rs │ ├── lib.rs │ ├── orm.rs │ └── user.rs ├── tonic-gen ├── Cargo.toml ├── src │ └── lib.rs └── build.rs ├── diesel.toml ├── limit-test-utils ├── Cargo.toml └── src │ └── lib.rs ├── .gitignore ├── README.zh-cn.md ├── limit-server-auth-test ├── Cargo.toml └── src │ └── lib.rs ├── limit-server-event ├── Cargo.toml └── src │ └── lib.rs ├── limit-server-auth ├── Cargo.toml └── src │ └── lib.rs ├── rustfmt.toml ├── limit-server-event-test ├── Cargo.toml └── src │ └── lib.rs ├── Cargo.toml ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── rust.yml ├── limit-deps ├── src │ └── lib.rs └── Cargo.toml ├── README.md └── Cargo.lock /migrations/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | DATABASE_URL=test.sqlite -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly-2022-12-06 -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod test; 3 | -------------------------------------------------------------------------------- /limit-server-subs/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub struct SubsService; 2 | -------------------------------------------------------------------------------- /integration_test_conf.toml: -------------------------------------------------------------------------------- 1 | [ports] 2 | available = "3000-3100" -------------------------------------------------------------------------------- /migrations/2022-10-19-004234_user/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE USER; 2 | -------------------------------------------------------------------------------- /migrations/2022-11-17-094920_event/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE EVENT; 2 | -------------------------------------------------------------------------------- /migrations/2022-11-17-095317_event_message/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE MESSAGE; 2 | -------------------------------------------------------------------------------- /limit-am/README.md: -------------------------------------------------------------------------------- 1 | # limit auth manager 2 | 3 | provide auth from server 4 | -------------------------------------------------------------------------------- /migrations/2022-10-19-225137_user_profile/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE USER_PROFILE; 2 | -------------------------------------------------------------------------------- /migrations/2022-11-11-195501_event_subscriptions/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE EVENT_SUBSCRIPTIONS; 2 | -------------------------------------------------------------------------------- /migrations/2022-10-19-225223_user_private_settings/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE USER_PRIVACY_SETTINGS; 2 | -------------------------------------------------------------------------------- /migrations/2022-10-19-225303_user_login_passcode/down.sql: -------------------------------------------------------------------------------- 1 | drop table USER_LOGIN_PASSCODE; 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "idl"] 2 | path = idl 3 | url = https://github.com/Limit-IM/limit-proto 4 | -------------------------------------------------------------------------------- /limit-am/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "limit-am" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | limit-deps = { path = "../limit-deps" } 8 | -------------------------------------------------------------------------------- /limit-utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "limit-utils" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | limit-deps = { path = "../limit-deps" } 8 | -------------------------------------------------------------------------------- /limit-config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "limit-config" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | limit-deps = { path = "../limit-deps" } 8 | -------------------------------------------------------------------------------- /migrations/2022-10-19-004234_user/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE USER( 2 | ID VARCHAR PRIMARY KEY NOT NULL, 3 | PUBKEY VARCHAR NOT NULL, 4 | SHAREDKEY VARCHAR NOT NULL 5 | ); 6 | -------------------------------------------------------------------------------- /migrations/2022-11-17-094920_event/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE EVENT( 2 | ID VARCHAR PRIMARY KEY NOT NULL, 3 | TS BIGINT NOT NULL, 4 | SENDER VARCHAR NOT NULL, 5 | EVENT_TYPE VARCHAR NOT NULL 6 | ); 7 | -------------------------------------------------------------------------------- /limit-db/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "limit-db" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | limit-config = { path = "../limit-config" } 8 | limit-deps = { path = "../limit-deps" } 9 | -------------------------------------------------------------------------------- /tonic-gen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tonic-gen" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | tonic = "0.8" 8 | prost = "0.11" 9 | 10 | [build-dependencies] 11 | tonic-build = "0.8" 12 | -------------------------------------------------------------------------------- /migrations/2022-10-19-225303_user_login_passcode/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE USER_LOGIN_PASSCODE( 2 | ID VARCHAR PRIMARY KEY NOT NULL, 3 | PASSCODE VARCHAR NOT NULL, 4 | 5 | FOREIGN KEY(ID) REFERENCES USER(ID) 6 | ); 7 | -------------------------------------------------------------------------------- /diesel.toml: -------------------------------------------------------------------------------- 1 | # For documentation on how to configure this file, 2 | # see https://diesel.rs/guides/configuring-diesel-cli 3 | 4 | [print_schema] 5 | file = "limit-db/src/schema.rs" 6 | 7 | [migrations_directory] 8 | dir = "migrations" 9 | -------------------------------------------------------------------------------- /migrations/2022-11-11-195501_event_subscriptions/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE EVENT_SUBSCRIPTIONS ( 2 | USER_ID VARCHAR PRIMARY KEY NOT NULL, 3 | -- CHANNEL NAME 4 | SUBSCRIBED_TO VARCHAR NOT NULL, 5 | -- CHANNEL TYPE 6 | CHANNEL_TYPE VARCHAR NOT NULL 7 | ); 8 | -------------------------------------------------------------------------------- /migrations/2022-11-17-095317_event_message/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE MESSAGE( 2 | EVENT_ID VARCHAR PRIMARY KEY NOT NULL, 3 | RECEIVER_ID VARCHAR NOT NULL, 4 | RECEIVER_SERVER VARCHAR NOT NULL, 5 | TEXT VARCHAR NOT NULL, 6 | -- SERIALIZED JSON 7 | EXTENSIONS VARCHAR NOT NULL, 8 | 9 | FOREIGN KEY(EVENT_ID) REFERENCES EVENT(ID) 10 | ); 11 | -------------------------------------------------------------------------------- /limit-test-utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "limit-test-utils" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | limit-deps = { path = "../limit-deps" } 8 | limit-am = { path = "../limit-am" } 9 | limit-server-auth = { path = "../limit-server-auth" } 10 | limit-db = { path = "../limit-db" } 11 | limit-config = { path = "../limit-config" } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | doc/ 3 | .DS_Store 4 | .idea/ 5 | .vscode/ 6 | 7 | *.code-workspace 8 | *.sqlite 9 | 10 | # Generated by Cargo 11 | # will have compiled files and executables 12 | debug/ 13 | target/ 14 | 15 | # These are backup files generated by rustfmt 16 | **/*.rs.bk 17 | 18 | # MSVC Windows builds of rustc generate these, which store debugging information 19 | *.pdb 20 | -------------------------------------------------------------------------------- /migrations/2022-10-19-225137_user_profile/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE USER_PROFILE( 2 | ID VARCHAR PRIMARY KEY NOT NULL, 3 | NAME VARCHAR NOT NULL, 4 | USER_NAME VARCHAR NOT NULL, 5 | BIO VARCHAR, 6 | -- URL 7 | AVATAR VARCHAR, 8 | -- DATE 9 | LAST_SEEN TEXT, 10 | -- DATE 11 | LAST_MODIFIED TEXT, 12 | 13 | FOREIGN KEY(ID) REFERENCES USER(ID) 14 | ); 15 | -------------------------------------------------------------------------------- /README.zh-cn.md: -------------------------------------------------------------------------------- 1 | ## 懒得看,你给我讲一下这是什么玩意儿,我这就给你点⭐! 2 | 这是一个新的IM协议,并与基于联邦的治理的实施。 3 | 4 | ## 等等!我可以在我的AWS EC2 T或Azure B1系列机器上托管它吗? 5 | 内存使用和存储使用以及部署的方便性是这个项目的 **第0级** 关注点。 6 | 所以它应该是可以在1C1G的小鸡上运行的。 7 | 8 | 另外,这个产品对云计算基础设施非常友好,所有的数据库和指标都考虑到了用户可能在云计算SaaS上部署。 9 | 10 | ## ~~如果我超级有钱~~它在我的k8s集群上的扩展性好吗? 11 | 横向扩展是一个非常大的挑战,更不用说我必须考虑独立部署的困难。 12 | 不能自动化的水平扩展对Ops来说就更可怕了,所以当我做单机部署版本时,尽力让它能扩展的。 13 | 在这个阶段,我将尝试解耦这些组件,然后尝试以集群友好的方式开发它们。 -------------------------------------------------------------------------------- /limit-server-auth-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "limit-server-auth-test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | limit-deps = { path = "../limit-deps" } 8 | limit-server-auth = { path = "../limit-server-auth" } 9 | limit-db = { path = "../limit-db" } 10 | limit-am = { path = "../limit-am" } 11 | limit-config = { path = "../limit-config" } 12 | limit-test-utils = { path = "../limit-test-utils" } 13 | -------------------------------------------------------------------------------- /limit-server-event/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "limit-server-event" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | tonic-gen = { path = "../tonic-gen" } 8 | 9 | limit-deps = { path = "../limit-deps" } 10 | limit-server-auth = { path = "../limit-server-auth" } 11 | limit-db = { path = "../limit-db" } 12 | limit-am = { path = "../limit-am" } 13 | limit-config = {path = "../limit-config"} 14 | limit-utils = {path = "../limit-utils"} 15 | -------------------------------------------------------------------------------- /limit-server-auth/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "limit-server-auth" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | tonic-gen = { path = "../tonic-gen" } 8 | 9 | limit-am = { path = "../limit-am" } 10 | limit-config = { path = "../limit-config" } 11 | limit-db = { path = "../limit-db" } 12 | limit-deps = { path = "../limit-deps" } 13 | limit-utils = { path = "../limit-utils" } 14 | 15 | [dev-dependencies] 16 | limit-test-utils = {path = "../limit-test-utils"} 17 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | 3 | version = "Two" 4 | 5 | group_imports = "StdExternalCrate" 6 | imports_granularity = "Crate" 7 | reorder_imports = true 8 | 9 | wrap_comments = true 10 | normalize_comments = true 11 | 12 | reorder_impl_items = true 13 | condense_wildcard_suffixes = true 14 | enum_discrim_align_threshold = 20 15 | use_field_init_shorthand = true 16 | 17 | format_strings = true 18 | format_code_in_doc_comments = true 19 | format_macro_matchers = true -------------------------------------------------------------------------------- /migrations/2022-10-19-225223_user_private_settings/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE USER_PRIVACY_SETTINGS( 2 | ID VARCHAR PRIMARY KEY NOT NULL, 3 | -- VISIBILITY 4 | AVATAR VARCHAR NOT NULL, 5 | -- VISIBILITY 6 | LAST_SEEN VARCHAR NOT NULL, 7 | -- VISIBILITY 8 | JOINED_GROUPS VARCHAR NOT NULL, 9 | -- VISIBILITY 10 | FORWARDS VARCHAR NOT NULL, 11 | -- DURATION 12 | JWT_EXPIRATION VARCHAR NOT NULL, 13 | 14 | FOREIGN KEY(ID) REFERENCES USER(ID) 15 | ); 16 | -------------------------------------------------------------------------------- /tonic-gen/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod auth { 2 | tonic::include_proto!("limit.auth"); 3 | } 4 | 5 | pub mod event { 6 | tonic::include_proto!("limit.event"); 7 | pub mod types { 8 | tonic::include_proto!("limit.event.types"); 9 | } 10 | } 11 | 12 | pub mod subs { 13 | tonic::include_proto!("limit.subs"); 14 | pub mod types { 15 | tonic::include_proto!("limit.subs.types"); 16 | } 17 | } 18 | 19 | pub mod utils { 20 | tonic::include_proto!("limit.utils"); 21 | } 22 | -------------------------------------------------------------------------------- /limit-server-event-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "limit-server-event-test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | limit-server-auth = { path = "../limit-server-auth" } 8 | limit-server-event = { path = "../limit-server-event" } 9 | limit-test-utils = { path = "../limit-test-utils" } 10 | limit-deps = { path = "../limit-deps" } 11 | limit-db = { path = "../limit-db" } 12 | limit-am = { path = "../limit-am" } 13 | limit-config = { path = "../limit-config" } 14 | limit-utils = { path = "../limit-utils" } 15 | -------------------------------------------------------------------------------- /limit-server-subs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "limit-server-subs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | tonic-gen = { path = "../tonic-gen" } 8 | 9 | limit-deps = { path = "../limit-deps" } 10 | limit-db = { path = "../limit-db" } 11 | limit-am = { path = "../limit-am" } 12 | limit-server-auth = { path = "../limit-server-auth" } 13 | limit-config = { path = "../limit-config" } 14 | limit-utils = { path = "../limit-utils" } 15 | 16 | [dev-dependencies] 17 | limit-test-utils = {path = "../limit-test-utils"} 18 | -------------------------------------------------------------------------------- /tonic-gen/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // auth.proto 3 | tonic_build::configure() 4 | .build_client(true) 5 | .build_server(true) 6 | .compile( 7 | &[ 8 | "../idl/auth.proto", 9 | "../idl/event.proto", 10 | "../idl/event.types.proto", 11 | "../idl/subs.proto", 12 | "../idl/subs.types.proto", 13 | "../idl/utils.proto", 14 | ], 15 | &["../idl"], 16 | ) 17 | .unwrap(); 18 | } 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "limit-server" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | limit-deps = { path = "./limit-deps" } 8 | 9 | [dev-dependencies] 10 | limit-server-auth-test = { path = "./limit-server-auth-test" } 11 | limit-server-event-test = { path = "./limit-server-event-test" } 12 | limit-test-utils = { path = "./limit-test-utils" } 13 | 14 | [workspace] 15 | members = [ 16 | "limit-*", 17 | "tonic-gen" 18 | ] 19 | 20 | [profile.release] 21 | opt-level = 3 22 | debug = true 23 | debug-assertions = false 24 | overflow-checks = false 25 | lto = true 26 | panic = 'unwind' 27 | incremental = false 28 | codegen-units = 1 29 | rpath = false 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F680 Feature request" 3 | about: Suggest a new feature in Move 4 | title: "[Feature Request]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | # 🚀 Feature Request 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 14 | 15 | **Describe the solution you'd like** 16 | A clear and concise description of what you want to happen. 17 | 18 | **Describe alternatives you've considered** 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | **Additional context** 22 | Add any other context or screenshots about the feature request here. 23 | -------------------------------------------------------------------------------- /limit-deps/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(custom_inner_attributes)] 2 | #![rustfmt::skip] 3 | 4 | // encryption 5 | pub use aes; 6 | pub use elliptic_curve; 7 | pub use jsonwebtoken; 8 | pub use p256; 9 | 10 | // serialization 11 | pub use serde; 12 | pub use serde_json; 13 | 14 | // utils 15 | pub use anyhow; 16 | pub use base64; 17 | pub use chrono; 18 | pub use once_cell; 19 | pub use rand; 20 | pub use toml; 21 | pub use uuid; 22 | pub use url; 23 | pub use mod_use; 24 | 25 | // logging 26 | pub use tracing; 27 | pub use tracing_subscriber; 28 | 29 | // I/O & async 30 | pub use async_trait; 31 | pub use crossbeam_channel; 32 | pub use tokio; 33 | pub use tokio_util; 34 | 35 | // rpc 36 | pub use hyper; 37 | pub use prost; 38 | pub use tonic; 39 | 40 | // middlewares 41 | pub use tower; 42 | 43 | // database 44 | pub use diesel; 45 | pub use futures; 46 | pub use r2d2; 47 | pub use r2d2_sqlite; 48 | pub use redis; 49 | 50 | // observability 51 | pub use metrics; 52 | -------------------------------------------------------------------------------- /limit-db/src/macros.rs: -------------------------------------------------------------------------------- 1 | /// ```rust 2 | /// let (redis_cluster, redis, db_pool) = get_db_layer!(req); 3 | /// ``` 4 | #[macro_export] 5 | macro_rules! get_db_layer { 6 | ($req:ident) => { 7 | ( 8 | // TODO: redis_cluster 9 | (), 10 | // redis 11 | $req.extensions() 12 | .get::() 13 | .context("no redis extended to service") 14 | .map_err(|e| { 15 | tracing::error!("{}", e); 16 | Status::internal(e.to_string()) 17 | })? 18 | .clone(), 19 | // db_pool 20 | $req.extensions() 21 | .get::() 22 | .context("no db extended to service") 23 | .map_err(|e| { 24 | tracing::error!("{}", e); 25 | Status::internal(e.to_string()) 26 | })? 27 | .clone(), 28 | ) 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: Create a bug report to help improve Move 4 | title: "[Bug]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | # 🐛 Bug 11 | 12 | **Describe the bug** 13 | A clear and concise description of what the bug is. 14 | 15 | **To Reproduce** 16 | Steps to reproduce the behavior: 17 | 1. Go to '...' 18 | 2. Click on '....' 19 | 3. Scroll down to '....' 20 | 4. See error 21 | 22 | **Expected behavior** 23 | A clear and concise description of what you expected to happen. 24 | 25 | **Screenshots** 26 | If applicable, add screenshots to help explain your problem. 27 | 28 | **Desktop (please complete the following information):** 29 | - OS: [e.g. iOS] 30 | - Browser [e.g. chrome, safari] 31 | - Version [e.g. 22] 32 | 33 | **Smartphone (please complete the following information):** 34 | - Device: [e.g. iPhone6] 35 | - OS: [e.g. iOS8.1] 36 | - Browser [e.g. stock browser, safari] 37 | - Version [e.g. 22] 38 | 39 | **Additional context** 40 | Add any other context about the problem here. 41 | -------------------------------------------------------------------------------- /src/test/mod.rs: -------------------------------------------------------------------------------- 1 | use limit_deps::*; 2 | use limit_test_utils::mock_config; 3 | 4 | macro_rules! tokio_run { 5 | ($e:expr) => { 6 | tokio::runtime::Builder::new_current_thread() 7 | .enable_all() 8 | .build() 9 | .unwrap() 10 | .block_on($e) 11 | }; 12 | } 13 | 14 | #[test] 15 | fn integration_test() { 16 | tracing_subscriber::fmt::init(); 17 | tracing::info!("⚠integration tests started⚠"); 18 | 19 | mock_config(); 20 | 21 | tokio_run!(async { 22 | let tasks = vec![ 23 | tokio::spawn(limit_server_auth_test::integration_test()), 24 | tokio::spawn(limit_server_event_test::integration_test()), 25 | ]; 26 | futures::future::join_all(tasks).await 27 | }) 28 | .into_iter() 29 | .for_each(|r| { 30 | if let Err(e) = r { 31 | tracing::error!("💥integration test failed💥: {}", e); 32 | panic!("💥integration test failed💥: {e}"); 33 | } 34 | }); 35 | tracing::info!("🎉integration tests finished🎉"); 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: ["master", "dev"] 6 | pull_request: 7 | types: 8 | - review_requested 9 | - ready_for_review 10 | - opened 11 | workflow_dispatch: 12 | 13 | env: 14 | CARGO_TERM_COLOR: always 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | if: ${{ !contains(github.event.head_commit.message, '[skip-ci]') }} 20 | steps: 21 | - name: Checkout source code 22 | uses: actions/checkout@v2 23 | with: 24 | submodules: 'recursive' 25 | - name: Install latest nightly 26 | uses: actions-rs/toolchain@v1 27 | with: 28 | override: true 29 | components: rustfmt, clippy 30 | - name: Install diesel_cli 31 | run: cargo install diesel_cli --no-default-features --features sqlite 32 | - name: Run diesel migration 33 | run: diesel migration run 34 | - name: Install protoc 35 | run: sudo apt install -y protobuf-compiler 36 | - name: Check 37 | run: cargo check --workspace --all-targets --all-features 38 | - name: Build 39 | run: cargo build 40 | - name: Start Redis 41 | uses: supercharge/redis-github-action@1.4.0 42 | with: 43 | redis-version: 6 44 | - name: Run tests 45 | run: cargo test -- --test-threads=1 46 | -------------------------------------------------------------------------------- /limit-deps/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "limit-deps" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | # encryption 8 | aes = "0.8" 9 | jsonwebtoken = "8.1" 10 | p256 = { version = "0.11", features = ["pem", "ecdh"] } 11 | elliptic-curve = { version = "0.12", features = ["pem", "ecdh"] } 12 | 13 | # serialization 14 | serde = { version = "1.0", features = ["derive"] } 15 | serde_json = "1.0" 16 | 17 | # utils 18 | anyhow = "1" 19 | rand = "0.8" 20 | toml = "0.5" 21 | base64 = "0.13" 22 | once_cell = "1.16" 23 | chrono = { version = "0.4", features = ["serde"] } 24 | 25 | # logging 26 | tracing = "0.1" 27 | tracing-subscriber = "0.3" 28 | 29 | # I/O & async 30 | tokio-util = "0.7" 31 | async-trait = "0.1" 32 | futures = "0.3" 33 | crossbeam-channel = "0.5" 34 | tokio = { version = "1", features = ["full"] } 35 | 36 | # rpc 37 | tonic = "0.8" 38 | prost = "0.11" 39 | hyper = "0.14" 40 | 41 | # middlewares 42 | tower = "0.4" 43 | 44 | # database 45 | r2d2 = "0.8" 46 | r2d2_sqlite = "0.21" 47 | 48 | # observability 49 | metrics = "0.20.1" 50 | url = { version = "2.3.1", features = ["serde"] } 51 | mod_use = "0.2.1" 52 | 53 | [dependencies.redis] 54 | version = "0.22" 55 | features = [ 56 | "aio", 57 | "cluster", 58 | "r2d2", 59 | "tokio-comp" 60 | ] 61 | 62 | [dependencies.diesel] 63 | version = "2.0" 64 | features = ["sqlite", "uuid", "chrono", "r2d2"] 65 | 66 | # utils 67 | [dependencies.uuid] 68 | version = "1.2" 69 | features = [ 70 | "v4", 71 | "v7", 72 | "fast-rng", 73 | "macro-diagnostics", 74 | "serde", 75 | ] 76 | -------------------------------------------------------------------------------- /limit-db/src/schema.rs: -------------------------------------------------------------------------------- 1 | // @generated automatically by Diesel CLI. 2 | 3 | diesel::table! { 4 | EVENT (ID) { 5 | ID -> Text, 6 | TS -> BigInt, 7 | SENDER -> Text, 8 | EVENT_TYPE -> Text, 9 | } 10 | } 11 | 12 | diesel::table! { 13 | EVENT_SUBSCRIPTIONS (USER_ID) { 14 | USER_ID -> Text, 15 | SUBSCRIBED_TO -> Text, 16 | CHANNEL_TYPE -> Text, 17 | } 18 | } 19 | 20 | diesel::table! { 21 | MESSAGE (EVENT_ID) { 22 | EVENT_ID -> Text, 23 | RECEIVER_ID -> Text, 24 | RECEIVER_SERVER -> Text, 25 | TEXT -> Text, 26 | EXTENSIONS -> Text, 27 | } 28 | } 29 | 30 | diesel::table! { 31 | USER (ID) { 32 | ID -> Text, 33 | PUBKEY -> Text, 34 | SHAREDKEY -> Text, 35 | } 36 | } 37 | 38 | diesel::table! { 39 | USER_LOGIN_PASSCODE (ID) { 40 | ID -> Text, 41 | PASSCODE -> Text, 42 | } 43 | } 44 | 45 | diesel::table! { 46 | USER_PRIVACY_SETTINGS (ID) { 47 | ID -> Text, 48 | AVATAR -> Text, 49 | LAST_SEEN -> Text, 50 | JOINED_GROUPS -> Text, 51 | FORWARDS -> Text, 52 | JWT_EXPIRATION -> Text, 53 | } 54 | } 55 | 56 | diesel::table! { 57 | USER_PROFILE (ID) { 58 | ID -> Text, 59 | NAME -> Text, 60 | USER_NAME -> Text, 61 | BIO -> Nullable, 62 | AVATAR -> Nullable, 63 | LAST_SEEN -> Nullable, 64 | LAST_MODIFIED -> Nullable, 65 | } 66 | } 67 | 68 | diesel::joinable!(MESSAGE -> EVENT (EVENT_ID)); 69 | diesel::joinable!(USER_LOGIN_PASSCODE -> USER (ID)); 70 | diesel::joinable!(USER_PRIVACY_SETTINGS -> USER (ID)); 71 | diesel::joinable!(USER_PROFILE -> USER (ID)); 72 | 73 | diesel::allow_tables_to_appear_in_same_query!( 74 | EVENT, 75 | EVENT_SUBSCRIPTIONS, 76 | MESSAGE, 77 | USER, 78 | USER_LOGIN_PASSCODE, 79 | USER_PRIVACY_SETTINGS, 80 | USER_PROFILE, 81 | ); 82 | -------------------------------------------------------------------------------- /limit-config/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{net::SocketAddr, path::PathBuf}; 2 | 3 | use limit_deps::{url::Url, *}; 4 | use once_cell::sync::OnceCell; 5 | use serde::{Deserialize, Serialize}; 6 | pub static GLOBAL_CONFIG: OnceCell = OnceCell::new(); 7 | 8 | #[derive(Debug, Clone, Serialize, Deserialize)] 9 | #[serde(crate = "limit_deps::serde")] 10 | pub enum Database { 11 | Sqlite { path: PathBuf }, 12 | Postgres { url: Url }, 13 | Mysql { url: Url }, 14 | } 15 | 16 | /// Deploy mode of the server 17 | #[derive(Debug, Clone, Serialize, Deserialize)] 18 | #[serde(crate = "limit_deps::serde")] 19 | pub enum DeployMode { 20 | /// standalone mode 21 | StandAlone { 22 | /// bind address 23 | addr: SocketAddr, 24 | }, 25 | /// master node of cluster 26 | Master { 27 | /// bind address 28 | addr: SocketAddr, 29 | /// Url of slave nodes 30 | slaves: Vec, 31 | }, 32 | /// the url of the master 33 | Slave { master: Url }, 34 | } 35 | 36 | #[derive(Debug, Clone, Serialize, Deserialize)] 37 | #[serde(crate = "limit_deps::serde")] 38 | pub enum Metrics { 39 | /// prometheus metrics 40 | Prometheus { url: Url }, 41 | /// both influxdb and victoria metrics 42 | InfluxDB { url: Url }, 43 | /// directly print metrics to terminal 44 | Terminal, 45 | } 46 | 47 | #[derive(Debug, Clone, Serialize, Deserialize)] 48 | #[serde(crate = "limit_deps::serde")] 49 | pub struct Config { 50 | /// server url 51 | pub url: String, 52 | 53 | /// Database config 54 | pub database: Database, 55 | 56 | /// Database connection pool thread count 57 | /// default is 3 58 | pub database_pool_thread_count: usize, 59 | 60 | /// metrics config 61 | pub metrics: Metrics, 62 | 63 | /// remember to set this to a random string 64 | /// also reset when you update the server 65 | pub jwt_secret: String, 66 | 67 | /// generated when you first run the server 68 | pub admin_jwt: String, 69 | 70 | /// server secret key 71 | pub server_secret_key: String, 72 | 73 | /// server public key 74 | pub server_public_key: String, 75 | 76 | /// per user message on-the-fly limit 77 | /// default is 100 78 | pub per_user_message_on_the_fly_limit: usize, 79 | } 80 | -------------------------------------------------------------------------------- /limit-db/src/event.rs: -------------------------------------------------------------------------------- 1 | use diesel::{Insertable, Queryable, Selectable}; 2 | use limit_deps::*; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::schema::*; 6 | 7 | /// A event for sending and receiving 8 | #[derive(Serialize, Deserialize)] 9 | #[serde(crate = "limit_deps::serde")] 10 | pub struct SREvent { 11 | pub head: Event, 12 | pub body: SREventBody, 13 | } 14 | 15 | #[derive(Serialize, Deserialize)] 16 | #[serde(crate = "limit_deps::serde")] 17 | pub enum SREventBody { 18 | Message(Message), 19 | } 20 | 21 | impl From<(Event, Message)> for SREvent { 22 | fn from(value: (Event, Message)) -> Self { 23 | Self { 24 | head: value.0, 25 | body: SREventBody::Message(value.1), 26 | } 27 | } 28 | } 29 | 30 | /// A event 31 | #[derive(Serialize, Deserialize, Clone, Queryable, Insertable, Selectable)] 32 | #[serde(crate = "limit_deps::serde")] 33 | #[diesel(table_name = EVENT)] 34 | pub struct Event { 35 | /// should be unique 36 | #[diesel(column_name = "ID")] 37 | #[diesel(serialize_as = crate::orm::Uuid)] 38 | pub id: String, 39 | /// the timestamp UTC of the message 40 | #[diesel(column_name = "TS")] 41 | pub timestamp: i64, 42 | /// the sender uuid 43 | #[diesel(column_name = "SENDER")] 44 | pub sender: String, 45 | /// the event type 46 | #[diesel(column_name = "EVENT_TYPE")] 47 | pub event_type: String, 48 | } 49 | 50 | /// A message 51 | #[derive(Serialize, Deserialize, Clone, Queryable, Insertable, Selectable)] 52 | #[serde(crate = "limit_deps::serde")] 53 | #[diesel(table_name = MESSAGE)] 54 | pub struct Message { 55 | /// should be unique 56 | #[diesel(column_name = "EVENT_ID")] 57 | #[diesel(serialize_as = crate::orm::Uuid)] 58 | pub event_id: String, 59 | #[diesel(column_name = "RECEIVER_ID")] 60 | pub receiver_id: String, 61 | /// the receiver server 62 | #[diesel(column_name = "RECEIVER_SERVER")] 63 | pub receiver_server: String, 64 | /// text message 65 | #[diesel(column_name = "TEXT")] 66 | pub text: String, // TODO: encrypted text message 67 | /// extensions in string json 68 | #[diesel(column_name = "EXTENSIONS")] 69 | pub extensions: String, 70 | } 71 | 72 | /// user subscribe to message queue 73 | #[derive(Serialize, Deserialize, Clone, Queryable, Insertable, Selectable)] 74 | #[serde(crate = "limit_deps::serde")] 75 | #[diesel(table_name = EVENT_SUBSCRIPTIONS)] 76 | pub struct EventSubscriptions { 77 | #[diesel(column_name = "USER_ID")] 78 | #[diesel(serialize_as = crate::orm::Uuid)] 79 | pub user_id: String, 80 | #[diesel(column_name = "SUBSCRIBED_TO")] 81 | pub sub_to: String, 82 | #[diesel(column_name = "CHANNEL_TYPE")] 83 | pub channel_type: String, 84 | } 85 | -------------------------------------------------------------------------------- /limit-db/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(type_alias_impl_trait)] 2 | #![feature(trait_alias)] 3 | #![allow(non_snake_case)] 4 | 5 | use std::{ 6 | future::Future, 7 | task::{Context, Poll}, 8 | }; 9 | 10 | use diesel::{r2d2::ConnectionManager, SqliteConnection}; 11 | use limit_deps::{hyper::Body, tonic::body::BoxBody, *}; 12 | use r2d2::Pool; 13 | use tower::Service; 14 | 15 | pub mod event; 16 | pub mod macros; 17 | pub mod orm; 18 | pub mod user; 19 | 20 | pub mod schema { 21 | use limit_deps::diesel; 22 | 23 | include!("schema.rs"); 24 | } 25 | 26 | pub type RedisClient = redis::Client; 27 | 28 | /// add extension DB to `hyper::Request` 29 | #[derive(Clone)] 30 | pub struct DBService { 31 | inner: Inner, 32 | pool: DBPool, 33 | redis_pool: RedisClient, 34 | } 35 | 36 | #[derive(Debug, Clone)] 37 | pub enum DBPool { 38 | Sqlite(Pool>), 39 | Postgres, 40 | Mysql, 41 | } 42 | 43 | impl DBPool { 44 | pub fn new(config: &limit_config::Config) -> Self { 45 | match &config.database { 46 | limit_config::Database::Sqlite { path } => { 47 | let manager = ConnectionManager::::new( 48 | path.to_str().expect("Invalid sqlite path"), 49 | ); 50 | let pool = Pool::builder() 51 | .test_on_check_out(true) 52 | .build(manager) 53 | .expect("Could not build connection pool"); 54 | Self::Sqlite(pool) 55 | } 56 | limit_config::Database::Postgres { url } => todo!("{}", url), 57 | limit_config::Database::Mysql { url } => todo!("{}", url), 58 | } 59 | } 60 | } 61 | 62 | #[macro_export] 63 | macro_rules! run_sql { 64 | ($pool:expr, $e:expr, $err:expr) => {{ 65 | let d = std::time::Instant::now(); 66 | match &$pool { 67 | limit_db::DBPool::Sqlite(pool) => { 68 | let conn = pool.get().map_err($err)?; 69 | let res = $e(conn); 70 | metrics::histogram!("database_sqlite_execution", d.elapsed()); 71 | res 72 | } 73 | limit_db::DBPool::Postgres => todo!(), 74 | limit_db::DBPool::Mysql => todo!(), 75 | } 76 | }}; 77 | } 78 | 79 | static GLOBAL_DB_POOL: once_cell::sync::Lazy = 80 | once_cell::sync::Lazy::new(|| DBPool::new(limit_config::GLOBAL_CONFIG.get().unwrap())); 81 | 82 | static GLOBAL_REDIS_CLIENT: once_cell::sync::Lazy = 83 | once_cell::sync::Lazy::new(|| redis::Client::open("redis://127.0.0.1:6379/").unwrap()); 84 | 85 | #[derive(Debug, Clone)] 86 | /// DB Service Layer 87 | pub struct DBLayer; 88 | 89 | pub trait HyperService = Service, Response = hyper::Response> + Clone; 90 | 91 | type DBServiceFut = impl Future>; 92 | 93 | impl Service> for DBService { 94 | type Error = S::Error; 95 | type Future = DBServiceFut; 96 | type Response = S::Response; 97 | 98 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 99 | self.inner.poll_ready(cx) 100 | } 101 | 102 | fn call(&mut self, mut req: hyper::Request) -> Self::Future { 103 | // This is necessary because tonic internally uses `tower::buffer::Buffer`. 104 | // See https://github.com/tower-rs/tower/issues/547#issuecomment-767629149 105 | // for details on why this is necessary 106 | let clone = self.inner.clone(); 107 | let mut inner = std::mem::replace(&mut self.inner, clone); 108 | 109 | req.extensions_mut().insert(self.pool.clone()); 110 | req.extensions_mut().insert(self.redis_pool.clone()); 111 | 112 | async move { inner.call(req).await } 113 | } 114 | } 115 | 116 | impl tower::Layer for DBLayer { 117 | type Service = DBService; 118 | 119 | fn layer(&self, inner: S) -> Self::Service { 120 | DBService { 121 | inner, 122 | pool: GLOBAL_DB_POOL.clone(), 123 | redis_pool: GLOBAL_REDIS_CLIENT.clone(), 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /limit-db/src/orm.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use diesel::{ 4 | backend::Backend, 5 | serialize::{self, Output, ToSql}, 6 | sql_types, AsExpression, FromSqlRow, 7 | }; 8 | use limit_deps::*; 9 | 10 | #[derive(Debug, FromSqlRow, AsExpression)] 11 | #[diesel(sql_type = sql_types::Text)] 12 | pub struct Uuid(pub String); 13 | 14 | impl From for Uuid { 15 | fn from(uuid: uuid::Uuid) -> Self { 16 | Self(uuid.to_string()) 17 | } 18 | } 19 | 20 | impl From for Uuid { 21 | fn from(val: String) -> Self { 22 | Uuid(uuid::Uuid::parse_str(val.as_str()).unwrap().to_string()) 23 | } 24 | } 25 | 26 | impl ToSql for Uuid 27 | where 28 | DB: Backend, 29 | String: ToSql, 30 | { 31 | fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> serialize::Result { 32 | self.0.to_sql(out) 33 | } 34 | } 35 | 36 | #[derive(Debug, FromSqlRow, AsExpression)] 37 | #[diesel(sql_type = sql_types::Text)] 38 | pub struct Duration(pub String); 39 | 40 | impl From for Duration { 41 | fn from(val: String) -> Self { 42 | Duration( 43 | std::time::Duration::from_secs(val.parse().unwrap()) 44 | .as_secs() 45 | .to_string(), 46 | ) 47 | } 48 | } 49 | 50 | impl From for Duration { 51 | fn from(duration: std::time::Duration) -> Self { 52 | Self(duration.as_secs().to_string()) 53 | } 54 | } 55 | 56 | impl From> for Duration { 57 | fn from(val: Option) -> Self { 58 | if let Some(s) = val { 59 | Duration( 60 | std::time::Duration::from_secs(s.parse().unwrap()) 61 | .as_secs() 62 | .to_string(), 63 | ) 64 | } else { 65 | Duration("0".into()) 66 | } 67 | } 68 | } 69 | 70 | impl ToSql for Duration 71 | where 72 | DB: Backend, 73 | String: ToSql, 74 | { 75 | fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> serialize::Result { 76 | self.0.to_sql(out) 77 | } 78 | } 79 | 80 | #[derive(Debug, FromSqlRow, AsExpression)] 81 | #[diesel(sql_type = sql_types::Text)] 82 | pub struct DateTime(pub String); 83 | 84 | impl From for DateTime { 85 | fn from(val: String) -> Self { 86 | DateTime( 87 | chrono::DateTime::parse_from_rfc3339(val.as_str()) 88 | .unwrap() 89 | .to_string(), 90 | ) 91 | } 92 | } 93 | 94 | impl From> for DateTime { 95 | fn from(s: Option) -> Self { 96 | if let Some(s) = s { 97 | Self( 98 | chrono::DateTime::parse_from_rfc3339(s.as_str()) 99 | .unwrap() 100 | .to_string(), 101 | ) 102 | } else { 103 | Self( 104 | chrono::DateTime::parse_from_rfc3339("1970-01-01T00:00:00Z") 105 | .unwrap() 106 | .to_string(), 107 | ) 108 | } 109 | } 110 | } 111 | 112 | impl From> for DateTime { 113 | fn from(datetime: chrono::DateTime) -> Self { 114 | Self(datetime.to_rfc3339()) 115 | } 116 | } 117 | 118 | impl ToSql for DateTime 119 | where 120 | DB: Backend, 121 | String: ToSql, 122 | { 123 | fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> serialize::Result { 124 | self.0.to_sql(out) 125 | } 126 | } 127 | 128 | #[derive(Debug, FromSqlRow, AsExpression)] 129 | #[diesel(sql_type = sql_types::Text)] 130 | pub struct Visibility(pub String); 131 | 132 | impl From for Visibility { 133 | fn from(visibility: crate::user::Visibility) -> Self { 134 | Self(visibility.to_string()) 135 | } 136 | } 137 | 138 | impl From for Visibility { 139 | fn from(val: String) -> Self { 140 | Visibility( 141 | crate::user::Visibility::from_str(val.as_str()) 142 | .unwrap() 143 | .to_string(), 144 | ) 145 | } 146 | } 147 | 148 | impl ToSql for Visibility 149 | where 150 | DB: Backend, 151 | String: ToSql, 152 | { 153 | fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> serialize::Result { 154 | self.0.to_sql(out) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /limit-test-utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | use crossbeam_channel::{Receiver, Sender}; 2 | use limit_deps::*; 3 | use once_cell::sync::Lazy; 4 | 5 | pub fn mock_config() -> limit_config::Config { 6 | use limit_config::*; 7 | use limit_server_auth::{JWTClaim, JWTSub}; 8 | use uuid::Uuid; 9 | 10 | GLOBAL_CONFIG 11 | .get_or_init(|| { 12 | let (server_secret_key, server_public_key) = limit_am::create_random_secret().unwrap(); 13 | Config { 14 | url: "127.0.0.1:1313".parse().unwrap(), 15 | database: Database::Sqlite { 16 | path: "test.sqlite".parse().unwrap(), 17 | }, 18 | jwt_secret: "mock".to_string(), 19 | database_pool_thread_count: 3, 20 | admin_jwt: jsonwebtoken::encode( 21 | &jsonwebtoken::Header::default(), 22 | &JWTClaim::new( 23 | JWTSub { 24 | id: Uuid::new_v4(), 25 | device_id: Uuid::new_v4().to_string(), 26 | }, 27 | chrono::Duration::days(1), 28 | ), 29 | &jsonwebtoken::EncodingKey::from_secret("mock_admin".as_bytes()), 30 | ) 31 | .unwrap(), 32 | metrics: Metrics::Terminal, 33 | server_secret_key, 34 | server_public_key, 35 | per_user_message_on_the_fly_limit: 100, 36 | } 37 | }) 38 | .clone() 39 | } 40 | 41 | pub static AVAILABLE_PORTS_CHANNEL: Lazy<(Sender, Receiver)> = Lazy::new(|| { 42 | let conf_str = std::fs::read_to_string("integration_test_conf.toml").unwrap(); 43 | let conf: toml::Value = toml::from_str(&conf_str).unwrap(); 44 | let ports = conf["ports"]["available"] 45 | .as_str() 46 | .unwrap() 47 | .split('-') 48 | .map(|number| number.parse::().unwrap()) 49 | .collect::>(); 50 | let (s, r) = crossbeam_channel::bounded((ports[1] - ports[0]) as _); 51 | (ports[0]..ports[1]).for_each(|p| s.send(p).unwrap()); 52 | (s, r) 53 | }); 54 | 55 | pub async fn get_available_port() -> u16 { 56 | let r = AVAILABLE_PORTS_CHANNEL.1.clone(); 57 | loop { 58 | if let Ok(p) = r.try_recv() { 59 | return p; 60 | } else { 61 | tokio::time::sleep(std::time::Duration::from_millis(300)).await; 62 | } 63 | } 64 | } 65 | 66 | #[macro_export] 67 | macro_rules! test_tasks { 68 | ($port: expr, $($task:expr),+ $(,)?) => { 69 | vec![$(Box::pin($task($port)) as Pin> + Send>>),+]; 70 | } 71 | } 72 | 73 | #[macro_export] 74 | macro_rules! init_test_client { 75 | ($client_type:ty, $client_builder:ty) => { 76 | use std::net::SocketAddr; 77 | 78 | use limit_test_utils::get_available_port; 79 | 80 | let addr: SocketAddr = format!("127.0.0.1:{}", port).parse().unwrap(); 81 | <$client_builder>::new(format!("{}", module_path!())) 82 | .address(addr) 83 | .build() 84 | }; 85 | } 86 | 87 | #[macro_export] 88 | macro_rules! test_service { 89 | ($port:expr, $server:expr, $tasks:expr) => { 90 | use std::net::SocketAddr; 91 | 92 | use limit_test_utils::get_available_port; 93 | tracing::info!("💪 test {} started", module_path!()); 94 | 95 | tracing::info!("🚀 test {} on port {}", module_path!(), $port); 96 | let addr: SocketAddr = format!("127.0.0.1:{}", $port).parse().unwrap(); 97 | let addr = addr.into(); 98 | let server = tokio::spawn(async move { 99 | let server = $server.serve(addr).await.unwrap(); 100 | }); 101 | tokio::time::sleep(std::time::Duration::from_millis(3000)).await; 102 | futures::future::join_all($tasks) 103 | .await 104 | .into_iter() 105 | .for_each(|r| { 106 | if let Err(e) = r { 107 | tracing::error!("💥integration test failed💥: {}", e); 108 | panic!("💥integration test failed💥: {}", e); 109 | } 110 | }); 111 | server.abort(); 112 | 113 | tracing::info!("🎉test {} finished🎉", module_path!()); 114 | }; 115 | } 116 | 117 | pub async fn do_with_port(f: F) -> T 118 | where 119 | F: FnOnce(u16) -> T, 120 | { 121 | let port = get_available_port().await; 122 | let res = f(port); 123 | AVAILABLE_PORTS_CHANNEL.0.clone().send(port).unwrap(); 124 | res 125 | } 126 | 127 | #[macro_export] 128 | macro_rules! do_with_port_m { 129 | ($f:expr) => {{ 130 | use limit_test_utils::{get_available_port, AVAILABLE_PORTS_CHANNEL}; 131 | let port = get_available_port().await; 132 | let res = $f(port); 133 | AVAILABLE_PORTS_CHANNEL.0.clone().send(port).unwrap(); 134 | res 135 | }}; 136 | } 137 | -------------------------------------------------------------------------------- /limit-utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, fmt::Debug, future::Future}; 2 | 3 | use limit_deps::{futures::FutureExt, metrics::histogram, tokio::time::Instant, *}; 4 | use once_cell::sync::Lazy; 5 | use tokio::sync::mpsc::Receiver; 6 | use tokio_util::sync::ReusableBoxFuture; 7 | 8 | #[derive(Debug)] 9 | pub enum ControlMessage { 10 | Stop, 11 | } 12 | 13 | #[derive(Debug)] 14 | pub struct BackgroundTask { 15 | name: String, 16 | task: ReusableBoxFuture<'static, ()>, 17 | } 18 | 19 | impl BackgroundTask { 20 | pub fn new(name: I, task: F) -> Self 21 | where 22 | I: Into, 23 | T: Debug, 24 | E: Error, 25 | F: Future> + Send + 'static, 26 | { 27 | let name = name.into(); 28 | let name2 = name.clone(); 29 | Self { 30 | task: ReusableBoxFuture::new(async { 31 | let m = Measurement::start(&name2); 32 | task.map(move |r| match r { 33 | Ok(t) => { 34 | tracing::info!("{name2} success"); 35 | tracing::debug!("{name2} returned ({t:?})"); 36 | } 37 | Err(e) => { 38 | tracing::error!("{name2} failed: {e}"); 39 | } 40 | }) 41 | .await; 42 | m.end(); 43 | }), 44 | name, 45 | } 46 | } 47 | } 48 | 49 | #[derive(Debug, Clone)] 50 | struct BackgroundWorker; 51 | 52 | impl BackgroundWorker { 53 | async fn event_loop( 54 | mut queue: Receiver, 55 | mut control: Receiver, 56 | ) { 57 | loop { 58 | tokio::select! { 59 | Some(task) = queue.recv() => { 60 | tracing::info!("background task {} started", task.name); 61 | tokio::spawn(task.task); 62 | } 63 | Some(msg) = control.recv() => { 64 | tracing::info!("received control message: {:?}", msg); 65 | match msg { 66 | ControlMessage::Stop => break, 67 | } 68 | } 69 | _ = tokio::time::sleep(std::time::Duration::from_millis(300)) => { 70 | } 71 | } 72 | } 73 | } 74 | } 75 | 76 | static GLOBAL_EVENT_LOOP: Lazy<( 77 | tokio::sync::mpsc::Sender, 78 | tokio::sync::mpsc::Sender, 79 | )> = Lazy::new(|| { 80 | let (queue, queue_r) = tokio::sync::mpsc::channel(100); 81 | let (control, control_r) = tokio::sync::mpsc::channel(100); 82 | futures::executor::block_on(async move { 83 | tokio::spawn(BackgroundWorker::event_loop(queue_r, control_r)); 84 | }); 85 | (queue, control) 86 | }); 87 | 88 | pub async fn batch_execute_background_tasks(tasks: Vec) { 89 | let tasks = tasks 90 | .into_iter() 91 | .map(|task| GLOBAL_EVENT_LOOP.0.send(task)) 92 | .collect::>(); 93 | futures::future::join_all(tasks).await; 94 | } 95 | 96 | pub async fn execute_background_task(task: BackgroundTask) { 97 | GLOBAL_EVENT_LOOP.0.send(task).await.unwrap(); 98 | } 99 | 100 | /// A guard that record multiple histograms on demand or on dropped. To properly 101 | /// record measurements without any noise, remember use [`Measurement::end`], or 102 | /// an `early_exit` event will be recorded. 103 | pub struct Measurement { 104 | name: String, 105 | start: Instant, 106 | ongoing: bool, 107 | } 108 | 109 | impl Measurement { 110 | pub fn start(name: impl Into) -> Self { 111 | Self { 112 | name: name.into(), 113 | start: Instant::now(), 114 | ongoing: true, 115 | } 116 | } 117 | 118 | /// Record current elapsed time and start a new measurement **without 119 | /// reset** the timer 120 | pub fn record(&mut self, new_name: impl Into) -> &mut Self { 121 | histogram!(self.name.clone(), self.start.elapsed()); 122 | self.name = new_name.into(); 123 | self 124 | } 125 | 126 | /// Record current elapsed time, start a new measurement and **reset** the 127 | /// timer 128 | pub fn renew(&mut self, new_name: impl Into) -> &mut Self { 129 | histogram!(self.name.clone(), self.start.elapsed()); 130 | self.name = new_name.into(); 131 | self.start = Instant::now(); 132 | self 133 | } 134 | 135 | /// Record current elapsed time and stop the measurement 136 | pub fn end(mut self) { 137 | let s = std::mem::take(&mut self.name); 138 | histogram!(s, self.start.elapsed()); 139 | self.ongoing = false; 140 | } 141 | } 142 | 143 | impl Drop for Measurement { 144 | fn drop(&mut self) { 145 | if self.ongoing { 146 | let s = std::mem::take(&mut self.name); 147 | histogram!(s, self.start.elapsed(), "status" => "early_exit"); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /limit-am/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | use aes::{ 4 | cipher::{BlockDecrypt, BlockEncrypt, KeyInit}, 5 | Aes256, 6 | }; 7 | use anyhow::Context; 8 | use elliptic_curve::{ecdh::SharedSecret, generic_array::GenericArray, sec1::ToEncodedPoint}; 9 | use limit_deps::*; 10 | use p256::{NistP256, PublicKey, SecretKey}; 11 | 12 | pub fn create_random_secret() -> Result<(String, String), Box> { 13 | let secret_key = SecretKey::random(&mut rand::rngs::OsRng); 14 | let der = secret_key.to_sec1_der().map_err(|err| err.to_string())?; 15 | let binding = secret_key.public_key().to_encoded_point(false); 16 | let derp = binding.as_bytes(); 17 | let der_base64 = base64::encode(&der); 18 | let derp_base64 = base64::encode(derp); 19 | Ok((der_base64, derp_base64)) 20 | } 21 | 22 | pub fn decode_secret(secret: &str) -> Result> { 23 | let der = base64::decode(secret).map_err(|err| err.to_string())?; 24 | let secret_key = SecretKey::from_sec1_der(&der).map_err(|err| err.to_string())?; 25 | Ok(secret_key) 26 | } 27 | 28 | pub fn decode_public(public: &str) -> Result> { 29 | let der = base64::decode(public).map_err(|err| err.to_string())?; 30 | let public_key = PublicKey::from_sec1_bytes(&der).map_err(|err| err.to_string())?; 31 | Ok(public_key) 32 | } 33 | 34 | pub fn key_exchange(privkey1: SecretKey, pubkey2: PublicKey) -> String { 35 | let shared_secret = 36 | elliptic_curve::ecdh::diffie_hellman(privkey1.to_nonzero_scalar(), pubkey2.as_affine()); 37 | let encoded = base64::encode(shared_secret.raw_secret_bytes()); 38 | encoded 39 | } 40 | 41 | pub fn decode_shared_key(encoded: String) -> SharedSecret { 42 | SharedSecret::from(*GenericArray::from_slice( 43 | base64::decode(encoded).unwrap_or_default().as_slice(), 44 | )) 45 | } 46 | 47 | pub fn aes256_encrypt_string(key: &str, plaintext: &str) -> Result> { 48 | let binding = base64::decode(key)?; 49 | let key = binding.as_slice(); 50 | let cipher = Aes256::new_from_slice(key).map_err(|err| err.to_string())?; 51 | let padding = 16 - (plaintext.as_bytes().len() % 16); 52 | let padded_text_bytes = [plaintext.as_bytes(), &[padding as u8; 16][..padding]].concat(); 53 | debug_assert!(padded_text_bytes.len() % 16 == 0); 54 | let res = padded_text_bytes 55 | .chunks(16) 56 | .flat_map(|chunk| { 57 | let mut block = GenericArray::clone_from_slice(chunk); 58 | cipher.encrypt_block(&mut block); 59 | block 60 | }) 61 | .collect::>(); 62 | Ok(base64::encode(res)) 63 | } 64 | 65 | pub fn aes256_decrypt_string(key: &str, ciphertext: &str) -> Result> { 66 | let binding = base64::decode(key)?; 67 | let key = binding.as_slice(); 68 | let cipher = Aes256::new_from_slice(key).map_err(|err| err.to_string())?; 69 | let plaintext = base64::decode(ciphertext)?; 70 | debug_assert!(plaintext.len() % 16 == 0); 71 | let plaintext = plaintext 72 | .chunks(16) 73 | .flat_map(|chunk| { 74 | let mut block = GenericArray::clone_from_slice(chunk); 75 | cipher.decrypt_block(&mut block); 76 | block 77 | }) 78 | .collect::>(); 79 | let padder = plaintext.len().checked_sub(1).context("arith error")?; 80 | let padding = plaintext[padder]; 81 | let plaintext = &plaintext[..plaintext.len() - padding as usize]; 82 | Ok(String::from_utf8(plaintext.to_vec())?) 83 | } 84 | 85 | #[test] 86 | fn test_key_exchange_encode_decode() { 87 | let (user1_secret, user1_public) = create_random_secret().unwrap(); 88 | let (user2_secret, user2_public) = create_random_secret().unwrap(); 89 | let user1_secret_decoded = decode_secret(&user1_secret).unwrap(); 90 | let user2_secret_decoded = decode_secret(&user2_secret).unwrap(); 91 | let user1_pubkey = decode_public(&user1_public).unwrap(); 92 | let user2_pubkey = decode_public(&user2_public).unwrap(); 93 | 94 | let user1_to_2_shared_secret = key_exchange(user1_secret_decoded, user2_pubkey); 95 | let user2_to_1_shared_secret = key_exchange(user2_secret_decoded, user1_pubkey); 96 | assert_eq!(user1_to_2_shared_secret, user2_to_1_shared_secret); 97 | println!("Shared secret: {user1_to_2_shared_secret}"); 98 | 99 | println!(); 100 | // user 1 send message to user 2 101 | let plaintext = "hello user 2 how do you do"; 102 | println!("plaintext: {plaintext}"); 103 | let ciphertext1_to_2 = aes256_encrypt_string(&user1_to_2_shared_secret, plaintext).unwrap(); 104 | println!("ciphertext: {ciphertext1_to_2}"); 105 | let decoded1_to_2 = 106 | aes256_decrypt_string(&user2_to_1_shared_secret, &ciphertext1_to_2).unwrap(); 107 | println!("decoded from user2: {decoded1_to_2}"); 108 | assert_eq!(plaintext, decoded1_to_2); 109 | 110 | println!(); 111 | // user 2 send message to user 1 112 | let plaintext = "hi user 1, nice to meet you"; 113 | println!("plaintext: {plaintext}"); 114 | let ciphertext2_to_1 = aes256_encrypt_string(&user2_to_1_shared_secret, plaintext).unwrap(); 115 | println!("ciphertext: {ciphertext2_to_1}"); 116 | let decoded2_to_1 = 117 | aes256_decrypt_string(&user1_to_2_shared_secret, &ciphertext2_to_1).unwrap(); 118 | println!("decoded from user1: {decoded2_to_1}"); 119 | assert_eq!(plaintext, decoded2_to_1); 120 | } 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LIMITS: Limit-IM does not have ITS LIMITS 2 | 3 | > We are undergoing a major refactoring and technology stack adjustment to better accommodate clustered deployment solutions 4 | 5 | 6 | [![Rust](https://github.com/Limit-IM/limit-server/actions/workflows/rust.yml/badge.svg)](https://github.com/Limit-IM/limit-server/actions/workflows/rust.yml) 7 | ![lines](https://tokei.ekzhang.com/b1/github/limit-im/limit-server) 8 | 9 | **Technology Stack** 10 | 11 | ![Rust](https://img.shields.io/badge/rust-%23000000.svg?style=for-the-badge&logo=rust&logoColor=white) 12 | ![Redis](https://img.shields.io/badge/redis-%23DD0031.svg?style=for-the-badge&logo=redis&logoColor=white) 13 | ![SQLite](https://img.shields.io/badge/sqlite-%2307405e.svg?style=for-the-badge&logo=sqlite&logoColor=white) 14 | ![JWT](https://img.shields.io/badge/JWT-black?style=for-the-badge&logo=JSON%20web%20tokens) 15 | ![Prometheus](https://img.shields.io/badge/Prometheus-E6522C?style=for-the-badge&logo=Prometheus&logoColor=white) 16 | ![Grafana](https://img.shields.io/badge/grafana-%23F46800.svg?style=for-the-badge&logo=grafana&logoColor=white) 17 | 18 | **Supported OS** 19 | 20 | ![macOS](https://img.shields.io/badge/mac%20os-000000?style=for-the-badge&logo=macos&logoColor=F0F0F0) 21 | ![Linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black) 22 | ![Windows](https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logo=windows&logoColor=white) 23 | 24 | 25 | `LIMITS` is yet another fully open source, interoperable, decentralised real-time communication protocol! 26 | 27 | --- 28 | [`中文文档`](README.zh-cn.md) 29 | 30 | ## TL;DR: What is this? 31 | 32 | It is a new IM protocol and with implementation with federal-based governance. 33 | 34 | In simple words, you can run your own server and connect to other servers quite easy. 35 | 36 | 37 | ## Progress of the project 38 | 39 | Since we started developing this project we have completed a lot of work and the current phase is still in the technical and theoretical validation phase. 40 | 41 | We first spent a month looking at existing IM protocols and we identified a number of issues such as centralisation, encryption, open source and private deployment etc. 42 | Secondly we spent a month looking at federation governance and we found a lot of issues such as how to get federation servers to communicate with each other, how to keep federation servers consistent with each other, how to synchronise data between federation servers etc. 43 | 44 | We have recently been working on the actual code and theoretical exercises and we hope to have the first version ready for development between March and April. 45 | 46 | 47 | ## Why are we doing this project? 48 | 49 | There are so many options for IM protocols nowadays, but they all have one problem in common: they are all centralised. 50 | This means that they are all controlled by a company or organisation that can close your account at any time, or modify your messages without your knowledge. 51 | This is a very serious problem as it can lead to a breach of your privacy, or theft of your account. 52 | 53 | Although many IMs claim to implement end-to-end encryption, some of these encryptions are really unknown to us or even some IM implementations do not turn on encryption by default, which leads to the possibility of your messages being eavesdropped on by third parties. 54 | Others are end-to-end encrypted but their server or client code is not open-sourced, so you don't know what it actually does and you can't verify its security yourself. 55 | 56 | Another disadvantage of not being open source is that it can't be deployed privately. For example, IM projects with a high level of privacy for internal use are usually not that user-friendly, and it's difficult for users to get started with a new IM framework, and it's not easy for companies to maintain a framework. 57 | 58 | So the problem we faced was that there was no IM protocol that combined decentralisation, encryption, open source and private deployment. 59 | 60 | 61 | ## What is our solution? 62 | 63 | We focus on high availability, high security, high scalability, high customisability and high ease of deployment. 64 | Our solution is a new IM protocol that is federation-based, which means it is made up of multiple servers, each of which is a separate entity that communicates with each other via a federation protocol. 65 | 66 | We also use relatively new technologies such as distributed databases, distributed caching, and CRDT state synchronisation. 67 | 68 | We have created a federated messaging protocol that runs on any server in the federation, which means you can deploy your clients on any server without worrying about your messages being intercepted by other servers. 69 | 70 | We use CRDT technology to synchronise state across the federation network, which means that a group can continue to operate with a minimum of one live server, providing a very high level of availability. 71 | 72 | 73 | ## Social Attributes 74 | 75 | Today's mainstream open source IMs do not have particularly strong social attributes. In order to achieve profitability and open source attributes at the same time we can combine blockchain technology to allow users to develop their own communities and create their own wealth such as emojis, wallpapers, themes, bubbles, etc. Moreover, the blockchain is open source and the server owner can choose to deploy it so that the server also has some profitability. This would allow both the server owner and the user to earn some revenue while maintaining the open source nature. 76 | 77 | 78 | ## Wait! Can I host it on my AWS EC2 T or Azure B1 series machine? 79 | 80 | Memory usage and storage usage and ease of deployment are the **Level 0** concerns for this project. 81 | So it should be able to run on a 1C1G machine. 82 | 83 | Also, this product is very cloud infrastructure friendly, with all the databases and metrics taken into account for possible user deployment on a cloud SaaS. 84 | 85 | 86 | ## Does it scale well on my k8s cluster? 87 | 88 | Scaling horizontally is a very big challenge, not to mention the difficulty I have to consider for standalone deployments. 89 | Horizontal scaling that can't be automated is even scarier for Ops, so when I do a standalone deployment version, do my best to make it scalable. 90 | At this stage I will try to decouple the components and then try to develop them in a cluster friendly way. 91 | -------------------------------------------------------------------------------- /limit-server-auth-test/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{future::Future, pin::Pin}; 2 | 3 | use diesel::RunQueryDsl; 4 | use limit_config::GLOBAL_CONFIG; 5 | use limit_db::{run_sql, schema::*, DBLayer, DBPool}; 6 | use limit_deps::{tonic::transport::Server, *}; 7 | use limit_server_auth::{ 8 | auth_service_client::AuthServiceClient, auth_service_server::AuthServiceServer, AuthService, 9 | DoAuthRequest, RequestAuthRequest, 10 | }; 11 | use limit_test_utils::{do_with_port, test_service, test_tasks}; 12 | 13 | pub async fn test_request_auth(port: u16) -> anyhow::Result<()> { 14 | tracing::info!("\t- test {}::test_request_auth started", module_path!()); 15 | 16 | let addr = format!("http://127.0.0.1:{port}"); 17 | let client = AuthServiceClient::connect(addr).await?; 18 | let id = uuid::Uuid::new_v4().to_string(); 19 | 20 | let rand_str = client.clone().request_auth(RequestAuthRequest { id }).await; 21 | 22 | tracing::info!("rand_str: {:?}", rand_str); 23 | assert!(rand_str.is_ok()); 24 | 25 | tracing::info!("\t- test {}::test_request_auth finished", module_path!()); 26 | Ok(()) 27 | } 28 | 29 | pub async fn test_do_auth(port: u16) -> anyhow::Result<()> { 30 | tracing::info!("\t- test {}::test_do_auth started", module_path!()); 31 | 32 | let id = uuid::Uuid::new_v4().to_string(); 33 | let device_id = uuid::Uuid::new_v4().to_string(); 34 | let (user_sec_key, user_pubkey) = limit_am::create_random_secret().unwrap(); 35 | let pubkey = limit_am::decode_public(&user_pubkey).unwrap(); 36 | let shared_key = limit_am::key_exchange( 37 | limit_am::decode_secret(&GLOBAL_CONFIG.get().unwrap().server_secret_key).unwrap(), 38 | pubkey, 39 | ); 40 | assert_eq!( 41 | shared_key, 42 | limit_am::key_exchange( 43 | limit_am::decode_secret(&user_sec_key).unwrap(), 44 | limit_am::decode_public(&GLOBAL_CONFIG.get().unwrap().server_public_key).unwrap() 45 | ) 46 | ); 47 | 48 | let user = limit_db::user::User { 49 | id: id.clone(), 50 | pubkey: user_pubkey, 51 | sharedkey: shared_key.clone(), 52 | }; 53 | 54 | let user_privacy_settings = limit_db::user::PrivacySettings { 55 | id: id.clone(), 56 | avatar: limit_db::orm::Visibility::from(limit_db::user::Visibility::Private).0, 57 | last_seen: limit_db::orm::Visibility::from(limit_db::user::Visibility::Private).0, 58 | groups: limit_db::orm::Visibility::from(limit_db::user::Visibility::Private).0, 59 | forwards: limit_db::orm::Visibility::from(limit_db::user::Visibility::Private).0, 60 | jwt_expiration: limit_db::orm::Duration::from(std::time::Duration::from_secs(114514)).0, 61 | }; 62 | let user_login_passcode = limit_db::user::UserLoginPasscode { 63 | id: id.clone(), 64 | passcode: "123456".to_string(), 65 | }; 66 | 67 | // set up db 68 | let config = || { 69 | let pool = DBPool::new(limit_config::GLOBAL_CONFIG.get().unwrap()); 70 | run_sql!( 71 | pool, 72 | |mut con| diesel::insert_into(USER::table) 73 | .values(user) 74 | .execute(&mut con) 75 | .unwrap(), 76 | |e| { 77 | tracing::error!("Error: {}", e); 78 | } 79 | ); 80 | run_sql!( 81 | pool, 82 | |mut con| diesel::insert_into(USER_PRIVACY_SETTINGS::table) 83 | .values(user_privacy_settings) 84 | .execute(&mut con) 85 | .unwrap(), 86 | |e| { 87 | tracing::error!("Error: {}", e); 88 | } 89 | ); 90 | run_sql!( 91 | pool, 92 | |mut con| diesel::insert_into(USER_LOGIN_PASSCODE::table) 93 | .values(user_login_passcode) 94 | .execute(&mut con) 95 | .unwrap(), 96 | |e| { 97 | tracing::error!("Error: {}", e); 98 | } 99 | ); 100 | Ok::<(), ()>(()) 101 | }; 102 | config().unwrap(); 103 | 104 | let addr = format!("http://127.0.0.1:{port}"); 105 | let mut client = AuthServiceClient::connect(addr).await?; 106 | 107 | // passcode is correct 108 | let passcode = limit_am::aes256_encrypt_string(&shared_key, "123456").unwrap(); 109 | let res = client 110 | .do_auth(DoAuthRequest { 111 | id: id.clone(), 112 | device_id: device_id.clone(), 113 | validated: passcode, 114 | }) 115 | .await; 116 | tracing::info!("res: {:?}", res); 117 | assert!(res.is_ok()); 118 | 119 | // passcode is incorrect 120 | let passcode = limit_am::aes256_encrypt_string(&shared_key, "1234567").unwrap(); 121 | let res = client 122 | .do_auth(DoAuthRequest { 123 | id: id.clone(), 124 | device_id: device_id.clone(), 125 | validated: passcode, 126 | }) 127 | .await; 128 | tracing::info!("res: {:?}", res); 129 | assert!(res.is_err()); 130 | 131 | // passcode is empty 132 | let passcode = "".to_string(); 133 | let res = client 134 | .do_auth(DoAuthRequest { 135 | id: id.clone(), 136 | device_id: device_id.clone(), 137 | validated: passcode, 138 | }) 139 | .await; 140 | tracing::info!("res: {:?}", res); 141 | assert!(res.is_err()); 142 | 143 | // passcode failed to decrypt 144 | let passcode = "123456".to_string(); 145 | let res = client 146 | .do_auth(DoAuthRequest { 147 | id: id.clone(), 148 | device_id: device_id.clone(), 149 | validated: passcode, 150 | }) 151 | .await; 152 | tracing::info!("res: {:?}", res); 153 | assert!(res.is_err()); 154 | 155 | tracing::info!("\t- test {}::test_do_auth finished", module_path!()); 156 | Ok(()) 157 | } 158 | 159 | pub async fn integration_test() { 160 | do_with_port(|port| async move { 161 | let tasks: Vec<_> = test_tasks![port, test_request_auth, test_do_auth,]; 162 | test_service! { 163 | port, 164 | Server::builder() 165 | .layer(DBLayer) 166 | .add_service(AuthServiceServer::new(AuthService)), 167 | tasks 168 | }; 169 | }) 170 | .await 171 | .await; 172 | } 173 | -------------------------------------------------------------------------------- /limit-db/src/user.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use diesel::prelude::*; 4 | use limit_deps::*; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::schema::*; 8 | 9 | /// A user 10 | #[derive(Serialize, Deserialize, Clone, Queryable, Insertable, Selectable)] 11 | #[serde(crate = "limit_deps::serde")] 12 | #[diesel(table_name = USER)] 13 | pub struct User { 14 | /// should be unique 15 | #[diesel(column_name = "ID")] 16 | #[diesel(serialize_as = crate::orm::Uuid)] 17 | pub id: String, 18 | // TODO: web3 approach 19 | /// the RSA public key of the user 20 | #[diesel(column_name = "PUBKEY")] 21 | pub pubkey: String, 22 | /// the shared key of the user 23 | #[diesel(column_name = "SHAREDKEY")] 24 | pub sharedkey: String, 25 | } 26 | 27 | /// A user's profile 28 | #[derive(Serialize, Deserialize, Clone, Queryable, Insertable, Selectable)] 29 | #[serde(crate = "limit_deps::serde")] 30 | #[diesel(table_name = USER_PROFILE)] 31 | pub struct Profile { 32 | /// foreign key to [`User`] 33 | #[diesel(column_name = "ID")] 34 | #[diesel(serialize_as = crate::orm::Uuid)] 35 | pub id: String, 36 | /// the user's name 37 | #[diesel(column_name = "NAME")] 38 | pub name: String, 39 | /// the user name on the server for @ and login 40 | #[diesel(column_name = "USER_NAME")] 41 | pub username: String, 42 | /// the user's bio 43 | #[diesel(column_name = "BIO")] 44 | pub bio: Option, 45 | // TODO: url or base64? 46 | /// the user's avatar 47 | /// if the avatar is not set, the client will use the None 48 | /// when query without permission, the client will return the None 49 | #[diesel(column_name = "AVATAR")] 50 | pub avatar: Option, 51 | /// last login time 52 | /// if the user never login, the server will return the register time 53 | /// when query without permission, the server will return the None 54 | #[diesel(column_name = "LAST_SEEN")] 55 | #[diesel(serialize_as = crate::orm::Duration)] 56 | pub last_seen: Option, 57 | /// the last time the user update the profile 58 | /// client should use this to check whether the profile is updated 59 | #[diesel(column_name = "LAST_MODIFIED")] 60 | #[diesel(serialize_as = crate::orm::DateTime)] 61 | pub last_modified: Option, 62 | } 63 | 64 | /// user login passcode 65 | #[derive(Serialize, Deserialize, Clone, Queryable, Insertable, Selectable)] 66 | #[serde(crate = "limit_deps::serde")] 67 | #[diesel(table_name = USER_LOGIN_PASSCODE)] 68 | pub struct UserLoginPasscode { 69 | /// foreign key to [`User`] 70 | #[diesel(column_name = "ID")] 71 | #[diesel(serialize_as = crate::orm::Uuid)] 72 | pub id: String, 73 | /// the user's random passcode 74 | #[diesel(column_name = "PASSCODE")] 75 | pub passcode: String, 76 | } 77 | 78 | /// A user's private settings 79 | #[derive(Serialize, Deserialize, Clone, Queryable, Insertable, Selectable)] 80 | #[serde(crate = "limit_deps::serde")] 81 | #[diesel(table_name = USER_PRIVACY_SETTINGS)] 82 | pub struct PrivacySettings { 83 | /// foreign key to [`User`] 84 | #[diesel(column_name = "ID")] 85 | #[diesel(serialize_as = crate::orm::Uuid)] 86 | pub id: String, 87 | /// check profile 88 | #[diesel(column_name = "AVATAR")] 89 | #[diesel(serialize_as = crate::orm::Visibility)] 90 | pub avatar: String, 91 | /// last time online 92 | #[diesel(column_name = "LAST_SEEN")] 93 | #[diesel(serialize_as = crate::orm::Visibility)] 94 | pub last_seen: String, 95 | /// group invites 96 | #[diesel(column_name = "JOINED_GROUPS")] 97 | #[diesel(serialize_as = crate::orm::Visibility)] 98 | pub groups: String, 99 | /// could forward messages to other users 100 | #[diesel(column_name = "FORWARDS")] 101 | #[diesel(serialize_as = crate::orm::Visibility)] 102 | pub forwards: String, 103 | /// minimum 24 hours, maximum 1 week 104 | #[diesel(column_name = "JWT_EXPIRATION")] 105 | #[diesel(serialize_as = crate::orm::Duration)] 106 | pub jwt_expiration: String, 107 | } 108 | 109 | /// The visibility of a field 110 | #[derive(Serialize, Deserialize, Clone)] 111 | #[serde(crate = "limit_deps::serde")] 112 | pub enum Visibility { 113 | Public, 114 | FriendsOnly, 115 | Private, 116 | } 117 | 118 | impl FromStr for Visibility { 119 | type Err = (); 120 | 121 | fn from_str(s: &str) -> Result { 122 | match s { 123 | "public" => Ok(Self::Public), 124 | "friends_only" => Ok(Self::FriendsOnly), 125 | "private" => Ok(Self::Private), 126 | _ => Err(()), 127 | } 128 | } 129 | } 130 | 131 | impl ToString for Visibility { 132 | fn to_string(&self) -> String { 133 | match self { 134 | Self::Public => "public".to_string(), 135 | Self::FriendsOnly => "friends_only".to_string(), 136 | Self::Private => "private".to_string(), 137 | } 138 | } 139 | } 140 | 141 | #[test] 142 | fn test_user_model() { 143 | use chrono::Utc; 144 | 145 | let id = crate::orm::Uuid::from(uuid::Uuid::new_v4()).0; 146 | let dummy_user = User { 147 | id: id.clone(), 148 | pubkey: "xdddd".to_string(), 149 | sharedkey: "xdddd".to_string(), 150 | }; 151 | 152 | let dummy_user_profile = Profile { 153 | id: id.clone(), 154 | name: "xdddd".to_string(), 155 | username: "xdddd".to_string(), 156 | bio: Some("xdddd".to_string()), 157 | avatar: Some("xdddd".to_string()), 158 | last_seen: Some(crate::orm::Duration::from(std::time::Duration::from_secs(100)).0), 159 | last_modified: Some(crate::orm::DateTime::from(Utc::now()).0), 160 | }; 161 | 162 | let mut con = diesel::sqlite::SqliteConnection::establish("../test.sqlite").unwrap(); 163 | let rows_inserted = diesel::insert_into(USER::table) 164 | .values(dummy_user) 165 | .execute(&mut con) 166 | .unwrap(); 167 | assert_eq!(rows_inserted, 1); 168 | 169 | let rows_inserted = diesel::insert_into(USER_PROFILE::table) 170 | .values(dummy_user_profile) 171 | .execute(&mut con) 172 | .unwrap(); 173 | assert_eq!(rows_inserted, 1); 174 | 175 | let users = USER::table.load::(&mut con).unwrap(); 176 | assert!(!users.is_empty()); 177 | let profile_of_username = USER_PROFILE::table 178 | .inner_join(USER::table) 179 | .filter(USER::PUBKEY.eq("xdddd")) 180 | .filter(USER_PROFILE::ID.eq(id)) 181 | .select(USER_PROFILE::all_columns) 182 | .load::(&mut con) 183 | .unwrap(); 184 | assert!(!profile_of_username.is_empty()); 185 | } 186 | -------------------------------------------------------------------------------- /limit-server-auth/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(string_remove_matches)] 2 | 3 | use anyhow::Context; 4 | use chrono::{Duration, Utc}; 5 | use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl}; 6 | use jsonwebtoken::{Algorithm, DecodingKey, Validation}; 7 | use limit_config::GLOBAL_CONFIG; 8 | use limit_db::{ 9 | get_db_layer, run_sql, 10 | schema::{USER, USER_LOGIN_PASSCODE, USER_PRIVACY_SETTINGS}, 11 | }; 12 | use limit_deps::{metrics::increment_counter, *}; 13 | use limit_utils::{execute_background_task, BackgroundTask, Measurement}; 14 | use serde::{Deserialize, Serialize}; 15 | use tonic::{Request, Response, Status}; 16 | pub use tonic_gen::auth::*; 17 | use uuid::Uuid; 18 | 19 | #[derive(Debug, Clone, PartialEq)] 20 | pub struct JWTSub { 21 | pub id: Uuid, 22 | pub device_id: String, 23 | } 24 | 25 | impl JWTSub { 26 | pub fn to_sub(&self) -> String { 27 | format!("{}/{}", self.device_id, self.id) 28 | } 29 | } 30 | 31 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 32 | #[serde(crate = "limit_deps::serde")] 33 | pub struct JWTClaim { 34 | /// device_id/uuid 35 | pub sub: String, 36 | /// expiration 37 | pub exp: i64, 38 | /// issue at 39 | pub iat: i64, 40 | } 41 | 42 | impl JWTClaim { 43 | pub fn new(sub: JWTSub, expire: Duration) -> Self { 44 | let iat = Utc::now(); 45 | let exp = iat + expire; 46 | 47 | Self { 48 | sub: sub.to_sub(), 49 | iat: iat.timestamp(), 50 | exp: exp.timestamp(), 51 | } 52 | } 53 | } 54 | 55 | pub fn decode_jwt(token: &str) -> Result { 56 | let validate = Validation::new(Algorithm::HS256); 57 | jsonwebtoken::decode::( 58 | token, 59 | &DecodingKey::from_secret(GLOBAL_CONFIG.get().unwrap().jwt_secret.as_bytes()), 60 | &validate, 61 | ) 62 | .map_err(|e| { 63 | tracing::error!("{}", e); 64 | Status::unauthenticated(e.to_string()) 65 | }) 66 | .map(|token| token.claims) 67 | } 68 | 69 | pub fn encode_jwt(claim: JWTClaim) -> Result { 70 | jsonwebtoken::encode( 71 | &jsonwebtoken::Header::default(), 72 | &claim, 73 | &jsonwebtoken::EncodingKey::from_secret(GLOBAL_CONFIG.get().unwrap().jwt_secret.as_bytes()), 74 | ) 75 | .map_err(|e| { 76 | tracing::error!("{}", e); 77 | Status::internal(e.to_string()) 78 | }) 79 | } 80 | 81 | #[test] 82 | fn test_encode_decode() { 83 | use limit_test_utils::mock_config; 84 | mock_config(); 85 | let claim = JWTClaim::new( 86 | JWTSub { 87 | id: Uuid::new_v4(), 88 | device_id: "test".to_string(), 89 | }, 90 | Duration::days(1), 91 | ); 92 | let token = encode_jwt(claim.clone()).unwrap(); 93 | let decoded = decode_jwt(&token).unwrap(); 94 | assert_eq!(claim, decoded); 95 | } 96 | 97 | fn generate_random_passcode() -> String { 98 | const POOL: &[u8] = &[ 99 | b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e', 100 | b'f', b'A', b'B', b'C', b'D', b'E', b'F', b'!', b'@', b'#', b'$', b'%', b'^', b'&', b'*', 101 | b'_', b'=', b'+', 102 | ]; 103 | const POOL_SIZE: usize = POOL.len(); 104 | 105 | use rand::Rng; 106 | 107 | let mut rng = rand::thread_rng(); 108 | let mut passcode = [0; 6]; 109 | 110 | passcode 111 | .iter_mut() 112 | .for_each(|b| *b = POOL[rng.gen_range(0..POOL_SIZE)]); 113 | 114 | // SAFETY: passcode is generated from a pool of ascii bytes 115 | unsafe { std::str::from_utf8_unchecked(&passcode) }.to_string() 116 | } 117 | 118 | #[test] 119 | fn test_generate_random_passcode() { 120 | let passcode = generate_random_passcode(); 121 | println!("{passcode}"); 122 | assert_eq!(passcode.len(), 6); 123 | } 124 | 125 | /// requires DB connection 126 | pub struct AuthService; 127 | 128 | #[tonic::async_trait] 129 | impl tonic_gen::auth::auth_service_server::AuthService for AuthService { 130 | async fn request_auth( 131 | &self, 132 | req: Request, 133 | ) -> Result, Status> { 134 | tracing::info!("request_auth: {:?}", req.get_ref().id); 135 | let mut m = Measurement::start("request_auth_generate_passcode"); 136 | let (_, redis, pool) = get_db_layer!(req); 137 | 138 | let id = req.get_ref().id.clone(); 139 | let _id_guard = id.parse::().map_err(|e| { 140 | tracing::error!("{}", e); 141 | Status::invalid_argument(e.to_string()) 142 | })?; 143 | 144 | let passcode = generate_random_passcode(); 145 | 146 | m.renew("request_auth_update_cache"); 147 | 148 | // update random passcode for user 149 | let _update_cache = redis::cmd("SET") 150 | .arg(format!("{id}:passcode")) 151 | .arg(&passcode) 152 | .execute(&mut redis.get_connection().map_err(|e| { 153 | tracing::error!("{}", e); 154 | Status::internal(e.to_string()) 155 | })?); 156 | 157 | m.renew("request_auth_update_diesel"); 158 | 159 | let sql = diesel::update(USER_LOGIN_PASSCODE::table) 160 | .filter(USER_LOGIN_PASSCODE::ID.eq(id)) 161 | .set(USER_LOGIN_PASSCODE::PASSCODE.eq(passcode.clone())); 162 | 163 | execute_background_task(BackgroundTask::new( 164 | "request_auth_update_user_passcode_db", 165 | async move { 166 | run_sql!( 167 | pool, 168 | |mut conn| { 169 | sql.execute(&mut conn).map_err(|e| { 170 | tracing::error!("{}", e); 171 | Status::internal(e.to_string()) 172 | }) 173 | }, 174 | |e| { 175 | tracing::error!("{}", e); 176 | Status::internal(e.to_string()) 177 | } 178 | ) 179 | }, 180 | )) 181 | .await; 182 | 183 | m.end(); 184 | 185 | Ok(Response::new(RequestAuthResponse { 186 | rand_text: passcode.clone(), 187 | })) 188 | } 189 | 190 | async fn do_auth(&self, req: Request) -> Result, Status> { 191 | tracing::info!( 192 | "do auth: {:?} at {:?}", 193 | req.get_ref().id, 194 | req.get_ref().device_id 195 | ); 196 | let mut m = Measurement::start("do_auth_load_auth"); 197 | let (_, mut redis, pool) = get_db_layer!(req); 198 | let id = req.get_ref().id.clone(); 199 | let uuid = uuid::Uuid::parse_str(&id).map_err(|e| { 200 | tracing::error!("{}", e); 201 | Status::invalid_argument(e.to_string()) 202 | })?; 203 | let passcode = &req.get_ref().validated; 204 | // get needed user info 205 | let sql_get_user_info = USER::table 206 | .inner_join(USER_PRIVACY_SETTINGS::table) 207 | .inner_join(USER_LOGIN_PASSCODE::table) 208 | .filter(USER::ID.eq(&id)) 209 | .select(( 210 | USER::ID, 211 | USER::SHAREDKEY, 212 | USER_LOGIN_PASSCODE::PASSCODE, 213 | USER_PRIVACY_SETTINGS::JWT_EXPIRATION, 214 | )); 215 | 216 | let res: (Option, Option, Option) = redis::pipe() 217 | .cmd("GET") 218 | .arg(format!("{id}:sharedkey")) 219 | .cmd("GET") 220 | .arg(format!("{id}:passcode")) 221 | .cmd("GET") 222 | .arg(format!("{id}:duration")) 223 | .query(&mut redis) 224 | .map_err(|e| { 225 | tracing::error!("{}", e); 226 | Status::internal(e.to_string()) 227 | })?; 228 | let (sharedkey, expected_passcode, duration) = if let (Some(sk), Some(ep), Some(dur)) = res 229 | { 230 | increment_counter!("do_auth_cache_hit"); 231 | tracing::info!("do_auth: cache hit for id {:?}", id); 232 | (sk, ep, dur) 233 | } else { 234 | increment_counter!("do_auth_cache_miss"); 235 | tracing::info!("do_auth: cache miss for id {:?}", id); 236 | let (id, sharedkey, expected_passcode, duration) = run_sql!( 237 | pool, 238 | |mut conn| { 239 | sql_get_user_info 240 | .first::<(String, String, String, String)>(&mut conn) 241 | .map_err(|e| { 242 | tracing::error!("{}", e); 243 | Status::internal(e.to_string()) 244 | }) 245 | }, 246 | |e| { 247 | tracing::error!("{}", e); 248 | Status::internal(e.to_string()) 249 | } 250 | )?; 251 | // update cache 252 | redis::pipe() 253 | .cmd("SET") 254 | .arg(format!("{id}:sharedkey")) 255 | .arg(&sharedkey) 256 | .cmd("SET") 257 | .arg(format!("{id}:passcode")) 258 | .arg(&expected_passcode) 259 | .cmd("SET") 260 | .arg(format!("{id}:duration")) 261 | .arg(&duration) 262 | .query(&mut redis) 263 | .map_err(|e| { 264 | tracing::error!("{}", e); 265 | Status::internal(e.to_string()) 266 | })?; 267 | (sharedkey, expected_passcode, duration) 268 | }; 269 | 270 | let expire = 271 | chrono::Duration::from_std(std::time::Duration::from_secs(duration.parse().unwrap())) 272 | .unwrap(); 273 | let decrypted = 274 | limit_am::aes256_decrypt_string(&sharedkey, passcode.as_str()).map_err(|e| { 275 | tracing::error!("{}", e); 276 | Status::internal(e.to_string()) 277 | })?; 278 | 279 | if decrypted == expected_passcode { 280 | m.renew("do_auth_gen_token"); 281 | tracing::info!("user login success: id: {}", id); 282 | let jwt = encode_jwt(JWTClaim::new( 283 | JWTSub { 284 | id: uuid, 285 | device_id: req.get_ref().device_id.clone(), 286 | }, 287 | expire, 288 | ))?; 289 | // update random passcode for user 290 | let _update_cache = redis::cmd("SET") 291 | .arg(format!("{id}:passcode")) 292 | .arg(passcode) 293 | .execute(&mut redis); 294 | let sql = diesel::update(USER_LOGIN_PASSCODE::table) 295 | .filter(USER_LOGIN_PASSCODE::ID.eq(id)) 296 | .set(USER_LOGIN_PASSCODE::PASSCODE.eq(generate_random_passcode())); 297 | 298 | execute_background_task(BackgroundTask::new( 299 | "do_auth_update_user_passcode_db", 300 | async move { 301 | run_sql!( 302 | pool, 303 | |mut conn| { 304 | sql.execute(&mut conn).map_err(|e| { 305 | tracing::error!("{}", e); 306 | Status::internal(e.to_string()) 307 | }) 308 | }, 309 | |e| { 310 | tracing::error!("{}", e); 311 | Status::internal(e.to_string()) 312 | } 313 | ) 314 | }, 315 | )) 316 | .await; 317 | m.end(); 318 | Ok(Response::new(Auth { jwt })) 319 | } else { 320 | // invalid passcode 321 | tracing::warn!("invalid passcode for id: {}", id); 322 | m.end(); 323 | Err(Status::unauthenticated("invalid passcode")) 324 | } 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /limit-server-event/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(iter_collect_into)] 2 | 3 | use anyhow::Context; 4 | use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl}; 5 | use futures::StreamExt; 6 | use limit_config::GLOBAL_CONFIG; 7 | use limit_db::{ 8 | get_db_layer, run_sql, 9 | schema::{EVENT, EVENT_SUBSCRIPTIONS, MESSAGE}, 10 | RedisClient, 11 | }; 12 | use limit_deps::{diesel::JoinOnDsl, *}; 13 | use limit_utils::{execute_background_task, BackgroundTask}; 14 | use tonic::{codegen::BoxStream, Request, Response, Status}; 15 | pub use tonic_gen::event::{event::*, synchronize_request::*, types::*, *}; 16 | 17 | #[derive(Debug, Clone)] 18 | // require db 19 | // require background worker 20 | // TODO: see if there is any message missing 21 | pub struct EventService; 22 | 23 | fn message_to_dbmessage(m: Event) -> limit_db::event::SREvent { 24 | let msg = match m.detail { 25 | Some(Detail::Message(ref m)) => m, 26 | _ => panic!(), 27 | }; 28 | ( 29 | limit_db::event::Event { 30 | id: m.event_id.clone(), 31 | timestamp: m.ts as i64, 32 | sender: m.sender, 33 | event_type: "message".to_string(), 34 | }, 35 | limit_db::event::Message { 36 | event_id: m.event_id, 37 | receiver_id: msg.receiver_id.to_owned(), 38 | receiver_server: msg.receiver_server.to_owned(), 39 | text: msg.text.to_owned(), 40 | extensions: serde_json::to_value(msg.extensions.to_owned()) 41 | .unwrap() 42 | .to_string(), 43 | }, 44 | ) 45 | .into() 46 | } 47 | 48 | fn dbmessage_to_message(m: limit_db::event::SREvent) -> Result { 49 | match m.head.event_type.as_str() { 50 | "message" => { 51 | let limit_db::event::SREventBody::Message(body) = m.body; 52 | Ok(Event { 53 | event_id: m.head.id, 54 | ts: m.head.timestamp as u64, 55 | sender: m.head.sender, 56 | detail: Some(Detail::Message(Message { 57 | receiver_id: body.receiver_id, 58 | receiver_server: body.receiver_server, 59 | text: body.text, 60 | extensions: serde_json::from_str(&body.extensions).unwrap(), 61 | })), 62 | }) 63 | } 64 | _ => Err(Status::internal("event type not supported")), 65 | } 66 | } 67 | 68 | #[tonic::async_trait] 69 | impl tonic_gen::event::event_service_server::EventService for EventService { 70 | type ReceiveEventsStream = BoxStream; 71 | 72 | async fn receive_events( 73 | &self, 74 | req: Request, 75 | ) -> Result, Status> { 76 | // check auth is valid 77 | let auth = req.get_ref().token.clone().ok_or_else(|| { 78 | tracing::error!("no auth token"); 79 | Status::unauthenticated("no auth token") 80 | })?; 81 | let claim = limit_server_auth::decode_jwt(&auth.jwt)?; 82 | let (_, id) = claim.sub.split_once('/').ok_or_else(|| { 83 | tracing::error!("invalid uuid"); 84 | Status::unauthenticated("invalid uuid") 85 | })?; 86 | 87 | let (_, redis, pool) = get_db_layer!(req); 88 | let mut redis_connection = redis.get_connection().map_err(|e| { 89 | tracing::error!("{}", e); 90 | Status::internal(e.to_string()) 91 | })?; 92 | let redis_async_connection = redis.get_async_connection().await.map_err(|e| { 93 | tracing::error!("{}", e); 94 | Status::internal(e.to_string()) 95 | })?; 96 | 97 | let subscriptions: Option> = redis::cmd("GET") 98 | .arg(format!("{id}:subscribed")) 99 | .query(&mut redis_connection) 100 | .map_err(|e| { 101 | tracing::error!("{}", e); 102 | Status::internal(e.to_string()) 103 | })?; 104 | let subscriptions = if let Some(subscriptions) = subscriptions { 105 | tracing::info!("receive message cache hit"); 106 | subscriptions 107 | } else { 108 | tracing::info!("receive message cache miss"); 109 | let sql = EVENT_SUBSCRIPTIONS::table.filter(EVENT_SUBSCRIPTIONS::USER_ID.eq(id)); 110 | let subs = run_sql!( 111 | pool, 112 | |mut conn| { 113 | sql.load::<(String, String, String)>(&mut conn) 114 | .map_err(|e| { 115 | tracing::error!("{}", e); 116 | Status::internal(e.to_string()) 117 | }) 118 | }, 119 | |e| { 120 | tracing::error!("{}", e); 121 | Status::internal(e.to_string()) 122 | } 123 | )? 124 | .into_iter() 125 | .map(|(_, a, c)| format!("{c}:{a}")) 126 | .collect::>(); 127 | subs 128 | }; 129 | 130 | let mut pubsub = redis_async_connection.into_pubsub(); 131 | for sub in subscriptions { 132 | pubsub.subscribe(sub).await.map_err(|e| { 133 | tracing::error!("{}", e); 134 | Status::internal(e.to_string()) 135 | })?; 136 | } 137 | let res = pubsub.into_on_message().map(|msg| { 138 | dbmessage_to_message( 139 | msg.get_payload() 140 | .map(|payload: String| serde_json::from_str(&payload).unwrap()) 141 | .map_err(|e| { 142 | tracing::error!("{}", e); 143 | Status::internal(e.to_string()) 144 | })?, 145 | ) 146 | }); 147 | Ok(Response::new(Box::pin(res))) 148 | } 149 | 150 | async fn send_event( 151 | &self, 152 | req: Request, 153 | ) -> Result, Status> { 154 | // check auth is valid 155 | let auth = req.get_ref().token.clone().ok_or_else(|| { 156 | tracing::error!("no auth token"); 157 | Status::unauthenticated("no auth token") 158 | })?; 159 | let _claim = limit_server_auth::decode_jwt(&auth.jwt)?; 160 | let event = req.get_ref().event.clone().ok_or_else(|| { 161 | tracing::error!("message is empty"); 162 | Status::cancelled("message is empty") 163 | })?; 164 | 165 | let current_server_url = GLOBAL_CONFIG.get().unwrap().url.as_str(); 166 | let mut message = event.clone(); 167 | message.event_id = uuid::Uuid::new_v4().to_string(); 168 | let message2 = message.clone(); 169 | 170 | let msg_detail = match event.detail { 171 | Some(Detail::Message(ref msg)) => msg, 172 | _ => return Err(Status::internal("no implementation")), 173 | }; 174 | 175 | if msg_detail.receiver_server == current_server_url { 176 | let mut redis = req 177 | .extensions() 178 | .get::() 179 | .context("no redis extended to service") 180 | .map_err(|e| { 181 | tracing::error!("{}", e); 182 | Status::internal(e.to_string()) 183 | })? 184 | .clone() 185 | .get_connection() 186 | .map_err(|e| { 187 | tracing::error!("{}", e); 188 | Status::internal(e.to_string()) 189 | })?; 190 | 191 | let message = message_to_dbmessage(message2); 192 | match message.head.event_type.as_str() { 193 | "message" => { 194 | let limit_db::event::SREventBody::Message(body) = &message.body; 195 | redis::cmd("PUBLISH") 196 | .arg(format!("message:{}", body.receiver_id)) 197 | .arg(serde_json::to_string(&message).unwrap()) 198 | .execute(&mut redis); 199 | 200 | // store message 201 | let pool = req 202 | .extensions() 203 | .get::() 204 | .context("no db extended to service") 205 | .map_err(|e| { 206 | tracing::error!("{}", e); 207 | Status::internal(e.to_string()) 208 | })? 209 | .clone(); 210 | let insert_event_sql = 211 | diesel::insert_into(EVENT::table).values(message.head.clone()); 212 | let insert_message_sql = 213 | diesel::insert_into(MESSAGE::table).values(body.clone()); 214 | let pool2 = pool.clone(); 215 | 216 | let _event_id = message.head.id.clone(); 217 | let _event_id2 = message.head.id.clone(); 218 | let event_id3 = message.head.id.clone(); 219 | execute_background_task(BackgroundTask::new("store_event", async move { 220 | run_sql!( 221 | pool, 222 | |mut conn| { 223 | insert_event_sql.execute(&mut conn).map_err(|e| { 224 | tracing::error!("{}", e); 225 | Status::internal(e.to_string()) 226 | }) 227 | }, 228 | |e| { 229 | tracing::error!("{}", e); 230 | Status::internal(e.to_string()) 231 | } 232 | ) 233 | })) 234 | .await; 235 | 236 | execute_background_task(BackgroundTask::new("store_message", async move { 237 | run_sql!( 238 | pool2, 239 | |mut conn| { 240 | insert_message_sql.execute(&mut conn).map_err(|e| { 241 | tracing::error!("{}", e); 242 | Status::internal(e.to_string()) 243 | }) 244 | }, 245 | |e| { 246 | tracing::error!("{}", e); 247 | Status::internal(e.to_string()) 248 | } 249 | ) 250 | })) 251 | .await; 252 | Ok(Response::new(SendEventResponse { 253 | event_id: event_id3, 254 | })) 255 | } 256 | _ => Err(Status::internal("message type not supported")), 257 | } 258 | } else { 259 | todo!("send to other server") 260 | } 261 | } 262 | 263 | async fn synchronize( 264 | &self, 265 | req: Request, 266 | ) -> Result, Status> { 267 | let sync_req = req.get_ref(); 268 | let (_, _, db_pool) = get_db_layer!(req); 269 | // check auth is valid 270 | let auth = sync_req.token.as_ref().ok_or_else(|| { 271 | tracing::error!("no auth token"); 272 | Status::unauthenticated("no auth token") 273 | })?; 274 | 275 | let claim = limit_server_auth::decode_jwt(&auth.jwt)?; 276 | let id = claim 277 | .sub 278 | .split_once('/') 279 | .map(|(_, id)| id.to_string()) 280 | .ok_or_else(|| { 281 | tracing::error!("invalid uuid"); 282 | Status::unauthenticated("invalid uuid") 283 | })?; 284 | 285 | let from = sync_req.from.as_ref().ok_or_else(|| { 286 | tracing::error!("no from"); 287 | Status::invalid_argument("no from") 288 | })?; 289 | let to = sync_req.to.as_ref().ok_or_else(|| { 290 | tracing::error!("no to"); 291 | Status::invalid_argument("no to") 292 | })?; 293 | let sql = EVENT::table 294 | .left_join( 295 | MESSAGE::table 296 | .inner_join(EVENT_SUBSCRIPTIONS::table.on(EVENT_SUBSCRIPTIONS::USER_ID.eq(id))), 297 | ) 298 | // filter message 299 | .filter(MESSAGE::RECEIVER_ID.eq(EVENT_SUBSCRIPTIONS::SUBSCRIBED_TO)) 300 | .filter(EVENT_SUBSCRIPTIONS::CHANNEL_TYPE.eq("message")) 301 | .order(EVENT::ID.desc()); 302 | 303 | let mut sql_id_id = None; 304 | let mut sql_id_ts = None; 305 | let mut sql_ts_id = None; 306 | let mut sql_ts_ts = None; 307 | 308 | let count = match sync_req.count { 309 | 1..=8192 => sync_req.count as i64, 310 | _ => 50, 311 | }; 312 | 313 | match from { 314 | From::IdFrom(from_id) => { 315 | let sql = sql.filter(EVENT::ID.gt(from_id)); 316 | match to { 317 | To::IdTo(to_id) => { 318 | sql_id_id = Some(sql.filter(EVENT::ID.le(to_id)).limit(count)); 319 | } 320 | To::TsTo(to_ts) => { 321 | sql_id_ts = Some(sql.filter(EVENT::TS.le((*to_ts) as i64)).limit(count)); 322 | } 323 | } 324 | } 325 | From::TsFrom(from_ts) => { 326 | let sql = sql.filter(EVENT::TS.gt((*from_ts) as i64)); 327 | match to { 328 | To::IdTo(to_id) => { 329 | sql_ts_id = Some(sql.filter(EVENT::ID.le(to_id)).limit(count)); 330 | } 331 | To::TsTo(to_ts) => { 332 | sql_ts_ts = Some(sql.filter(EVENT::TS.le((*to_ts) as i64)).limit(count)); 333 | } 334 | } 335 | } 336 | }; 337 | 338 | macro_rules! send_sync { 339 | ($e:expr) => { 340 | if let Some(sql) = $e { 341 | let res : Vec<(limit_db::event::Event, Option<(limit_db::event::Message, limit_db::event::EventSubscriptions)>)> = run_sql!( 342 | db_pool, 343 | |mut conn| { 344 | // tracing::info!("sql: {:?}", diesel::debug_query::(&sql)); 345 | sql.load::<(limit_db::event::Event, Option<(limit_db::event::Message, limit_db::event::EventSubscriptions)>)>(&mut conn).map_err(|e| { 346 | tracing::error!("{}", e); 347 | Status::internal(e.to_string()) 348 | }) 349 | }, 350 | |e| { 351 | tracing::error!("{}", e); 352 | Status::internal(e.to_string()) 353 | } 354 | )?; 355 | let mut ret = Vec::with_capacity(count as usize); 356 | res.into_iter().map(|(event, message)| { 357 | match (message,) { 358 | (Some((body, _)),) => { 359 | Event { 360 | event_id : event.id, 361 | ts : event.timestamp as u64, 362 | sender : event.sender, 363 | detail : Some(Detail::Message(Message { 364 | receiver_id: body.receiver_id, 365 | receiver_server: body.receiver_server, 366 | text: body.text, 367 | extensions: serde_json::from_str(&body.extensions).unwrap(), 368 | })), 369 | } 370 | } 371 | _ => todo!() 372 | } 373 | }) 374 | .collect_into(&mut ret); 375 | return Ok(Response::new(SynchronizeResponse { 376 | events: ret, 377 | })); 378 | }; 379 | }; 380 | } 381 | 382 | send_sync!(sql_id_id); 383 | send_sync!(sql_id_ts); 384 | send_sync!(sql_ts_id); 385 | send_sync!(sql_ts_ts); 386 | 387 | Err(Status::internal("no implementation")) 388 | } 389 | } 390 | -------------------------------------------------------------------------------- /limit-server-event-test/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{future::Future, pin::Pin}; 2 | 3 | use diesel::RunQueryDsl; 4 | use futures::StreamExt; 5 | use limit_config::GLOBAL_CONFIG; 6 | use limit_db::{ 7 | event::EventSubscriptions, 8 | run_sql, 9 | schema::{EVENT_SUBSCRIPTIONS, USER, USER_LOGIN_PASSCODE, USER_PRIVACY_SETTINGS}, 10 | DBLayer, DBPool, 11 | }; 12 | use limit_deps::{tonic::transport::Server, *}; 13 | use limit_server_auth::{ 14 | auth_service_client::AuthServiceClient, auth_service_server::AuthServiceServer, AuthService, 15 | DoAuthRequest, 16 | }; 17 | use limit_server_event::{ 18 | event_service_client::EventServiceClient, event_service_server::EventServiceServer, Detail, 19 | Event, EventService, From, Message, ReceiveEventsRequest, SendEventRequest, SynchronizeRequest, 20 | To, 21 | }; 22 | use limit_test_utils::{do_with_port, test_service, test_tasks}; 23 | 24 | pub fn add(left: usize, right: usize) -> usize { 25 | left + right 26 | } 27 | 28 | pub async fn test_send_message(port: u16) -> anyhow::Result<()> { 29 | tracing::info!("\t- test {}::test_send_message started", module_path!()); 30 | let _device_id = uuid::Uuid::new_v4().to_string(); 31 | let (user_sec_key, user_pubkey) = limit_am::create_random_secret().unwrap(); 32 | let pubkey = limit_am::decode_public(&user_pubkey).unwrap(); 33 | let shared_key = limit_am::key_exchange( 34 | limit_am::decode_secret(&GLOBAL_CONFIG.get().unwrap().server_secret_key).unwrap(), 35 | pubkey, 36 | ); 37 | assert_eq!( 38 | shared_key, 39 | limit_am::key_exchange( 40 | limit_am::decode_secret(&user_sec_key).unwrap(), 41 | limit_am::decode_public(&GLOBAL_CONFIG.get().unwrap().server_public_key).unwrap() 42 | ) 43 | ); 44 | tokio::time::sleep(std::time::Duration::from_millis(3000)).await; 45 | let device_id = uuid::Uuid::new_v4().to_string(); 46 | // set up user1 47 | let id = uuid::Uuid::new_v4().to_string(); 48 | let id1 = id.clone(); 49 | { 50 | let user = limit_db::user::User { 51 | id: id.clone(), 52 | pubkey: user_pubkey.clone(), 53 | sharedkey: shared_key.clone(), 54 | }; 55 | 56 | let user_privacy_settings = limit_db::user::PrivacySettings { 57 | id: id.clone(), 58 | avatar: limit_db::orm::Visibility::from(limit_db::user::Visibility::Private).0, 59 | last_seen: limit_db::orm::Visibility::from(limit_db::user::Visibility::Private).0, 60 | groups: limit_db::orm::Visibility::from(limit_db::user::Visibility::Private).0, 61 | forwards: limit_db::orm::Visibility::from(limit_db::user::Visibility::Private).0, 62 | jwt_expiration: limit_db::orm::Duration::from(std::time::Duration::from_secs(114514)).0, 63 | }; 64 | let user_login_passcode = limit_db::user::UserLoginPasscode { 65 | id: id.clone(), 66 | passcode: "123456".to_string(), 67 | }; 68 | 69 | // set up db 70 | let config = || { 71 | let pool = DBPool::new(limit_config::GLOBAL_CONFIG.get().unwrap()); 72 | run_sql!( 73 | pool, 74 | |mut con| diesel::insert_into(USER::table) 75 | .values(user) 76 | .execute(&mut con) 77 | .unwrap(), 78 | |e| { 79 | tracing::error!("Error: {}", e); 80 | } 81 | ); 82 | run_sql!( 83 | pool, 84 | |mut con| diesel::insert_into(USER_PRIVACY_SETTINGS::table) 85 | .values(user_privacy_settings) 86 | .execute(&mut con) 87 | .unwrap(), 88 | |e| { 89 | tracing::error!("Error: {}", e); 90 | } 91 | ); 92 | run_sql!( 93 | pool, 94 | |mut con| diesel::insert_into(USER_LOGIN_PASSCODE::table) 95 | .values(user_login_passcode) 96 | .execute(&mut con) 97 | .unwrap(), 98 | |e| { 99 | tracing::error!("Error: {}", e); 100 | } 101 | ); 102 | Ok::<(), ()>(()) 103 | }; 104 | config().unwrap(); 105 | } 106 | // set up user2 107 | let id = uuid::Uuid::new_v4().to_string(); 108 | let id2 = id.clone(); 109 | { 110 | let user = limit_db::user::User { 111 | id: id.clone(), 112 | pubkey: user_pubkey.clone(), 113 | sharedkey: shared_key.clone(), 114 | }; 115 | 116 | let user_privacy_settings = limit_db::user::PrivacySettings { 117 | id: id.clone(), 118 | avatar: limit_db::orm::Visibility::from(limit_db::user::Visibility::Private).0, 119 | last_seen: limit_db::orm::Visibility::from(limit_db::user::Visibility::Private).0, 120 | groups: limit_db::orm::Visibility::from(limit_db::user::Visibility::Private).0, 121 | forwards: limit_db::orm::Visibility::from(limit_db::user::Visibility::Private).0, 122 | jwt_expiration: limit_db::orm::Duration::from(std::time::Duration::from_secs(114514)).0, 123 | }; 124 | let user_login_passcode = limit_db::user::UserLoginPasscode { 125 | id: id.clone(), 126 | passcode: "123456".to_string(), 127 | }; 128 | 129 | // set up db 130 | let config = || { 131 | let pool = DBPool::new(limit_config::GLOBAL_CONFIG.get().unwrap()); 132 | run_sql!( 133 | pool, 134 | |mut con| diesel::insert_into(USER::table) 135 | .values(user) 136 | .execute(&mut con) 137 | .unwrap(), 138 | |e| { 139 | tracing::error!("Error: {}", e); 140 | } 141 | ); 142 | run_sql!( 143 | pool, 144 | |mut con| diesel::insert_into(USER_PRIVACY_SETTINGS::table) 145 | .values(user_privacy_settings) 146 | .execute(&mut con) 147 | .unwrap(), 148 | |e| { 149 | tracing::error!("Error: {}", e); 150 | } 151 | ); 152 | run_sql!( 153 | pool, 154 | |mut con| diesel::insert_into(USER_LOGIN_PASSCODE::table) 155 | .values(user_login_passcode) 156 | .execute(&mut con) 157 | .unwrap(), 158 | |e| { 159 | tracing::error!("Error: {}", e); 160 | } 161 | ); 162 | run_sql!( 163 | pool, 164 | |mut con| diesel::insert_into(EVENT_SUBSCRIPTIONS::table) 165 | .values(EventSubscriptions { 166 | user_id: id.clone(), 167 | sub_to: id.clone(), 168 | channel_type: "message".to_string(), 169 | }) 170 | .execute(&mut con) 171 | .unwrap(), 172 | |e| { 173 | tracing::error!("Error: {}", e); 174 | } 175 | ); 176 | Ok::<(), ()>(()) 177 | }; 178 | config().unwrap(); 179 | } 180 | let addr = format!("http://127.0.0.1:{port}"); 181 | let mut auth_client = AuthServiceClient::connect(addr.clone()).await?; 182 | let passcode = limit_am::aes256_encrypt_string(&shared_key, "123456").unwrap(); 183 | let res = auth_client 184 | .do_auth(DoAuthRequest { 185 | id: id1.clone(), 186 | device_id: device_id.clone(), 187 | validated: passcode.clone(), 188 | }) 189 | .await; 190 | let auth1 = res.unwrap(); 191 | let res = auth_client 192 | .do_auth(DoAuthRequest { 193 | id: id2.clone(), 194 | device_id: device_id.clone(), 195 | validated: passcode, 196 | }) 197 | .await; 198 | let auth2 = res.unwrap(); 199 | let mut client1 = EventServiceClient::connect(addr.clone()).await?; 200 | let mut client2 = EventServiceClient::connect(addr.clone()).await?; 201 | let receive = client2 202 | .receive_events(ReceiveEventsRequest { 203 | token: Some(auth2.get_ref().clone()), 204 | }) 205 | .await; 206 | assert!(receive.is_ok()); 207 | tracing::info!("client {:?} online", id2); 208 | tracing::info!("client {:?} sending message", id1); 209 | let send_message = client1 210 | .send_event(SendEventRequest { 211 | token: Some(auth1.get_ref().clone()), 212 | event: Some(Event { 213 | event_id: "".to_string(), 214 | ts: chrono::Utc::now().timestamp_millis() as u64, 215 | sender: id1.clone(), 216 | detail: Some(Detail::Message(Message { 217 | receiver_id: id2, 218 | receiver_server: GLOBAL_CONFIG.get().unwrap().url.clone(), 219 | text: "hello".to_string(), 220 | extensions: Default::default(), 221 | })), 222 | }), 223 | }) 224 | .await; 225 | assert!(send_message.is_ok()); 226 | tracing::info!("client {:?} message sent", id1); 227 | 228 | let received = receive 229 | .unwrap() 230 | .get_mut() 231 | .next() 232 | .await 233 | .unwrap() 234 | .unwrap() 235 | .detail 236 | .unwrap(); 237 | 238 | let received = match received { 239 | Detail::Message(ref m) => &m.text, 240 | }; 241 | 242 | assert_eq!(received, "hello"); 243 | tracing::info!("\t- test {}::test_send_message finished", module_path!()); 244 | Ok(()) 245 | } 246 | 247 | pub async fn test_sync_message(port: u16) -> anyhow::Result<()> { 248 | tracing::info!("\t- test {}::test_sync_message started", module_path!()); 249 | let send_ts = chrono::Utc::now().timestamp_millis(); 250 | let _device_id = uuid::Uuid::new_v4().to_string(); 251 | let (user_sec_key, user_pubkey) = limit_am::create_random_secret().unwrap(); 252 | let pubkey = limit_am::decode_public(&user_pubkey).unwrap(); 253 | let shared_key = limit_am::key_exchange( 254 | limit_am::decode_secret(&GLOBAL_CONFIG.get().unwrap().server_secret_key).unwrap(), 255 | pubkey, 256 | ); 257 | assert_eq!( 258 | shared_key, 259 | limit_am::key_exchange( 260 | limit_am::decode_secret(&user_sec_key).unwrap(), 261 | limit_am::decode_public(&GLOBAL_CONFIG.get().unwrap().server_public_key).unwrap() 262 | ) 263 | ); 264 | tokio::time::sleep(std::time::Duration::from_millis(3000)).await; 265 | let device_id = uuid::Uuid::new_v4().to_string(); 266 | // set up user1 267 | let id = uuid::Uuid::new_v4().to_string(); 268 | let id1 = id.clone(); 269 | { 270 | let user = limit_db::user::User { 271 | id: id.clone(), 272 | pubkey: user_pubkey.clone(), 273 | sharedkey: shared_key.clone(), 274 | }; 275 | 276 | let user_privacy_settings = limit_db::user::PrivacySettings { 277 | id: id.clone(), 278 | avatar: limit_db::orm::Visibility::from(limit_db::user::Visibility::Private).0, 279 | last_seen: limit_db::orm::Visibility::from(limit_db::user::Visibility::Private).0, 280 | groups: limit_db::orm::Visibility::from(limit_db::user::Visibility::Private).0, 281 | forwards: limit_db::orm::Visibility::from(limit_db::user::Visibility::Private).0, 282 | jwt_expiration: limit_db::orm::Duration::from(std::time::Duration::from_secs(114514)).0, 283 | }; 284 | let user_login_passcode = limit_db::user::UserLoginPasscode { 285 | id: id.clone(), 286 | passcode: "123456".to_string(), 287 | }; 288 | 289 | // set up db 290 | let config = || { 291 | let pool = DBPool::new(limit_config::GLOBAL_CONFIG.get().unwrap()); 292 | run_sql!( 293 | pool, 294 | |mut con| diesel::insert_into(USER::table) 295 | .values(user) 296 | .execute(&mut con) 297 | .unwrap(), 298 | |e| { 299 | tracing::error!("Error: {}", e); 300 | } 301 | ); 302 | run_sql!( 303 | pool, 304 | |mut con| diesel::insert_into(USER_PRIVACY_SETTINGS::table) 305 | .values(user_privacy_settings) 306 | .execute(&mut con) 307 | .unwrap(), 308 | |e| { 309 | tracing::error!("Error: {}", e); 310 | } 311 | ); 312 | run_sql!( 313 | pool, 314 | |mut con| diesel::insert_into(USER_LOGIN_PASSCODE::table) 315 | .values(user_login_passcode) 316 | .execute(&mut con) 317 | .unwrap(), 318 | |e| { 319 | tracing::error!("Error: {}", e); 320 | } 321 | ); 322 | Ok::<(), ()>(()) 323 | }; 324 | config().unwrap(); 325 | } 326 | // set up user2 327 | let id = uuid::Uuid::new_v4().to_string(); 328 | let id2 = id.clone(); 329 | { 330 | let user = limit_db::user::User { 331 | id: id.clone(), 332 | pubkey: user_pubkey.clone(), 333 | sharedkey: shared_key.clone(), 334 | }; 335 | 336 | let user_privacy_settings = limit_db::user::PrivacySettings { 337 | id: id.clone(), 338 | avatar: limit_db::orm::Visibility::from(limit_db::user::Visibility::Private).0, 339 | last_seen: limit_db::orm::Visibility::from(limit_db::user::Visibility::Private).0, 340 | groups: limit_db::orm::Visibility::from(limit_db::user::Visibility::Private).0, 341 | forwards: limit_db::orm::Visibility::from(limit_db::user::Visibility::Private).0, 342 | jwt_expiration: limit_db::orm::Duration::from(std::time::Duration::from_secs(114514)).0, 343 | }; 344 | let user_login_passcode = limit_db::user::UserLoginPasscode { 345 | id: id.clone(), 346 | passcode: "123456".to_string(), 347 | }; 348 | 349 | // set up db 350 | let config = || { 351 | let pool = DBPool::new(limit_config::GLOBAL_CONFIG.get().unwrap()); 352 | run_sql!( 353 | pool, 354 | |mut con| diesel::insert_into(USER::table) 355 | .values(user) 356 | .execute(&mut con) 357 | .unwrap(), 358 | |e| { 359 | tracing::error!("Error: {}", e); 360 | } 361 | ); 362 | run_sql!( 363 | pool, 364 | |mut con| diesel::insert_into(USER_PRIVACY_SETTINGS::table) 365 | .values(user_privacy_settings) 366 | .execute(&mut con) 367 | .unwrap(), 368 | |e| { 369 | tracing::error!("Error: {}", e); 370 | } 371 | ); 372 | run_sql!( 373 | pool, 374 | |mut con| diesel::insert_into(USER_LOGIN_PASSCODE::table) 375 | .values(user_login_passcode) 376 | .execute(&mut con) 377 | .unwrap(), 378 | |e| { 379 | tracing::error!("Error: {}", e); 380 | } 381 | ); 382 | run_sql!( 383 | pool, 384 | |mut con| diesel::insert_into(EVENT_SUBSCRIPTIONS::table) 385 | .values(EventSubscriptions { 386 | user_id: id.clone(), 387 | sub_to: id.clone(), 388 | channel_type: "message".to_string(), 389 | }) 390 | .execute(&mut con) 391 | .unwrap(), 392 | |e| { 393 | tracing::error!("Error: {}", e); 394 | } 395 | ); 396 | Ok::<(), ()>(()) 397 | }; 398 | config().unwrap(); 399 | } 400 | let addr = format!("http://127.0.0.1:{port}"); 401 | let mut auth_client = AuthServiceClient::connect(addr.clone()).await?; 402 | let passcode = limit_am::aes256_encrypt_string(&shared_key, "123456").unwrap(); 403 | let res = auth_client 404 | .do_auth(DoAuthRequest { 405 | id: id1.clone(), 406 | device_id: device_id.clone(), 407 | validated: passcode.clone(), 408 | }) 409 | .await; 410 | let auth1 = res.unwrap(); 411 | let res = auth_client 412 | .do_auth(DoAuthRequest { 413 | id: id2.clone(), 414 | device_id: device_id.clone(), 415 | validated: passcode, 416 | }) 417 | .await; 418 | let auth2 = res.unwrap(); 419 | let mut client1 = EventServiceClient::connect(addr.clone()).await?; 420 | let mut client2 = EventServiceClient::connect(addr).await?; 421 | 422 | tracing::info!("client {:?} online", id2); 423 | tracing::info!("client {:?} sending message", id1); 424 | let send_message = client1 425 | .send_event(SendEventRequest { 426 | token: Some(auth1.get_ref().clone()), 427 | event: Some(Event { 428 | event_id: "".to_string(), 429 | ts: chrono::Utc::now().timestamp_millis() as u64, 430 | sender: id1.clone(), 431 | detail: Some(Detail::Message(Message { 432 | receiver_id: id2.clone(), 433 | receiver_server: GLOBAL_CONFIG.get().unwrap().url.clone(), 434 | text: "1".to_string(), 435 | extensions: Default::default(), 436 | })), 437 | }), 438 | }) 439 | .await; 440 | assert!(send_message.is_ok()); 441 | tracing::info!("client {:?} message sent", id1); 442 | let send_message = client1 443 | .send_event(SendEventRequest { 444 | token: Some(auth1.get_ref().clone()), 445 | event: Some(Event { 446 | event_id: "".to_string(), 447 | ts: chrono::Utc::now().timestamp_millis() as u64, 448 | sender: id1.clone(), 449 | detail: Some(Detail::Message(Message { 450 | receiver_id: id2.clone(), 451 | receiver_server: GLOBAL_CONFIG.get().unwrap().url.clone(), 452 | text: "2".to_string(), 453 | extensions: Default::default(), 454 | })), 455 | }), 456 | }) 457 | .await; 458 | assert!(send_message.is_ok()); 459 | tracing::info!("client {:?} message sent", id1); 460 | let send_message = client1 461 | .send_event(SendEventRequest { 462 | token: Some(auth1.get_ref().clone()), 463 | event: Some(Event { 464 | event_id: "".to_string(), 465 | ts: chrono::Utc::now().timestamp_millis() as u64, 466 | sender: id1.clone(), 467 | detail: Some(Detail::Message(Message { 468 | receiver_id: id2.clone(), 469 | receiver_server: GLOBAL_CONFIG.get().unwrap().url.clone(), 470 | text: "3".to_string(), 471 | extensions: Default::default(), 472 | })), 473 | }), 474 | }) 475 | .await; 476 | assert!(send_message.is_ok()); 477 | tracing::info!("client {:?} message sent", id1); 478 | tokio::time::sleep(std::time::Duration::from_secs(5)).await; 479 | let sync = client2 480 | .synchronize(SynchronizeRequest { 481 | token: Some(auth2.get_ref().clone()), 482 | count: 50, 483 | from: Some(From::TsFrom(send_ts as u64)), 484 | to: Some(To::TsTo(chrono::Utc::now().timestamp_millis() as u64)), 485 | }) 486 | .await; 487 | assert!(sync.is_ok()); 488 | let sync = sync.unwrap(); 489 | assert!(sync.get_ref().events.len() >= 3); 490 | tracing::info!("sync messages: {:#?}", sync.get_ref().events); 491 | 492 | tracing::info!("\t- test {}::test_sync_message finished", module_path!()); 493 | Ok(()) 494 | } 495 | 496 | pub async fn integration_test() { 497 | do_with_port(|port| async move { 498 | let tasks: Vec<_> = test_tasks![port, test_send_message, test_sync_message]; 499 | 500 | test_service! { 501 | port, 502 | Server::builder() 503 | .layer(DBLayer) 504 | .add_service(AuthServiceServer::new(AuthService)) 505 | .add_service(EventServiceServer::new(EventService)), 506 | tasks 507 | }; 508 | }) 509 | .await 510 | .await; 511 | } 512 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aes" 7 | version = "0.8.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" 10 | dependencies = [ 11 | "cfg-if", 12 | "cipher", 13 | "cpufeatures", 14 | ] 15 | 16 | [[package]] 17 | name = "ahash" 18 | version = "0.7.6" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" 21 | dependencies = [ 22 | "getrandom", 23 | "once_cell", 24 | "version_check", 25 | ] 26 | 27 | [[package]] 28 | name = "android_system_properties" 29 | version = "0.1.5" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 32 | dependencies = [ 33 | "libc", 34 | ] 35 | 36 | [[package]] 37 | name = "anyhow" 38 | version = "1.0.66" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" 41 | 42 | [[package]] 43 | name = "async-stream" 44 | version = "0.3.3" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" 47 | dependencies = [ 48 | "async-stream-impl", 49 | "futures-core", 50 | ] 51 | 52 | [[package]] 53 | name = "async-stream-impl" 54 | version = "0.3.3" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" 57 | dependencies = [ 58 | "proc-macro2", 59 | "quote", 60 | "syn", 61 | ] 62 | 63 | [[package]] 64 | name = "async-trait" 65 | version = "0.1.58" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" 68 | dependencies = [ 69 | "proc-macro2", 70 | "quote", 71 | "syn", 72 | ] 73 | 74 | [[package]] 75 | name = "atomic" 76 | version = "0.5.1" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" 79 | dependencies = [ 80 | "autocfg", 81 | ] 82 | 83 | [[package]] 84 | name = "autocfg" 85 | version = "1.1.0" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 88 | 89 | [[package]] 90 | name = "axum" 91 | version = "0.6.1" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "08b108ad2665fa3f6e6a517c3d80ec3e77d224c47d605167aefaa5d7ef97fa48" 94 | dependencies = [ 95 | "async-trait", 96 | "axum-core", 97 | "bitflags", 98 | "bytes", 99 | "futures-util", 100 | "http", 101 | "http-body", 102 | "hyper", 103 | "itoa", 104 | "matchit", 105 | "memchr", 106 | "mime", 107 | "percent-encoding", 108 | "pin-project-lite", 109 | "rustversion", 110 | "serde", 111 | "sync_wrapper", 112 | "tower", 113 | "tower-http", 114 | "tower-layer", 115 | "tower-service", 116 | ] 117 | 118 | [[package]] 119 | name = "axum-core" 120 | version = "0.3.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "79b8558f5a0581152dc94dcd289132a1d377494bdeafcd41869b3258e3e2ad92" 123 | dependencies = [ 124 | "async-trait", 125 | "bytes", 126 | "futures-util", 127 | "http", 128 | "http-body", 129 | "mime", 130 | "rustversion", 131 | "tower-layer", 132 | "tower-service", 133 | ] 134 | 135 | [[package]] 136 | name = "base16ct" 137 | version = "0.1.1" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" 140 | 141 | [[package]] 142 | name = "base64" 143 | version = "0.13.1" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 146 | 147 | [[package]] 148 | name = "base64ct" 149 | version = "1.5.3" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" 152 | 153 | [[package]] 154 | name = "bitflags" 155 | version = "1.3.2" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 158 | 159 | [[package]] 160 | name = "block-buffer" 161 | version = "0.10.3" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" 164 | dependencies = [ 165 | "generic-array", 166 | ] 167 | 168 | [[package]] 169 | name = "bumpalo" 170 | version = "3.11.1" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" 173 | 174 | [[package]] 175 | name = "bytes" 176 | version = "1.3.0" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" 179 | 180 | [[package]] 181 | name = "cc" 182 | version = "1.0.77" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" 185 | 186 | [[package]] 187 | name = "cfg-if" 188 | version = "1.0.0" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 191 | 192 | [[package]] 193 | name = "chrono" 194 | version = "0.4.23" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" 197 | dependencies = [ 198 | "iana-time-zone", 199 | "js-sys", 200 | "num-integer", 201 | "num-traits", 202 | "serde", 203 | "time 0.1.44", 204 | "wasm-bindgen", 205 | "winapi", 206 | ] 207 | 208 | [[package]] 209 | name = "cipher" 210 | version = "0.4.3" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" 213 | dependencies = [ 214 | "crypto-common", 215 | "inout", 216 | ] 217 | 218 | [[package]] 219 | name = "codespan-reporting" 220 | version = "0.11.1" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 223 | dependencies = [ 224 | "termcolor", 225 | "unicode-width", 226 | ] 227 | 228 | [[package]] 229 | name = "combine" 230 | version = "4.6.6" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" 233 | dependencies = [ 234 | "bytes", 235 | "futures-core", 236 | "memchr", 237 | "pin-project-lite", 238 | "tokio", 239 | "tokio-util", 240 | ] 241 | 242 | [[package]] 243 | name = "const-oid" 244 | version = "0.9.1" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" 247 | 248 | [[package]] 249 | name = "core-foundation-sys" 250 | version = "0.8.3" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 253 | 254 | [[package]] 255 | name = "cpufeatures" 256 | version = "0.2.5" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" 259 | dependencies = [ 260 | "libc", 261 | ] 262 | 263 | [[package]] 264 | name = "crc16" 265 | version = "0.4.0" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "338089f42c427b86394a5ee60ff321da23a5c89c9d89514c829687b26359fcff" 268 | 269 | [[package]] 270 | name = "crossbeam-channel" 271 | version = "0.5.6" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" 274 | dependencies = [ 275 | "cfg-if", 276 | "crossbeam-utils", 277 | ] 278 | 279 | [[package]] 280 | name = "crossbeam-utils" 281 | version = "0.8.14" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" 284 | dependencies = [ 285 | "cfg-if", 286 | ] 287 | 288 | [[package]] 289 | name = "crypto-bigint" 290 | version = "0.4.9" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" 293 | dependencies = [ 294 | "generic-array", 295 | "rand_core", 296 | "subtle", 297 | "zeroize", 298 | ] 299 | 300 | [[package]] 301 | name = "crypto-common" 302 | version = "0.1.6" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 305 | dependencies = [ 306 | "generic-array", 307 | "typenum", 308 | ] 309 | 310 | [[package]] 311 | name = "cxx" 312 | version = "1.0.82" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453" 315 | dependencies = [ 316 | "cc", 317 | "cxxbridge-flags", 318 | "cxxbridge-macro", 319 | "link-cplusplus", 320 | ] 321 | 322 | [[package]] 323 | name = "cxx-build" 324 | version = "1.0.82" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0" 327 | dependencies = [ 328 | "cc", 329 | "codespan-reporting", 330 | "once_cell", 331 | "proc-macro2", 332 | "quote", 333 | "scratch", 334 | "syn", 335 | ] 336 | 337 | [[package]] 338 | name = "cxxbridge-flags" 339 | version = "1.0.82" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71" 342 | 343 | [[package]] 344 | name = "cxxbridge-macro" 345 | version = "1.0.82" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470" 348 | dependencies = [ 349 | "proc-macro2", 350 | "quote", 351 | "syn", 352 | ] 353 | 354 | [[package]] 355 | name = "der" 356 | version = "0.6.0" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "13dd2ae565c0a381dde7fade45fce95984c568bdcb4700a4fdbe3175e0380b2f" 359 | dependencies = [ 360 | "const-oid", 361 | "pem-rfc7468", 362 | "zeroize", 363 | ] 364 | 365 | [[package]] 366 | name = "diesel" 367 | version = "2.0.2" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "68c186a7418a2aac330bb76cde82f16c36b03a66fb91db32d20214311f9f6545" 370 | dependencies = [ 371 | "chrono", 372 | "diesel_derives", 373 | "libsqlite3-sys", 374 | "r2d2", 375 | "uuid", 376 | ] 377 | 378 | [[package]] 379 | name = "diesel_derives" 380 | version = "2.0.1" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "143b758c91dbc3fe1fdcb0dba5bd13276c6a66422f2ef5795b58488248a310aa" 383 | dependencies = [ 384 | "proc-macro-error", 385 | "proc-macro2", 386 | "quote", 387 | "syn", 388 | ] 389 | 390 | [[package]] 391 | name = "digest" 392 | version = "0.10.6" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" 395 | dependencies = [ 396 | "block-buffer", 397 | "crypto-common", 398 | "subtle", 399 | ] 400 | 401 | [[package]] 402 | name = "ecdsa" 403 | version = "0.14.8" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" 406 | dependencies = [ 407 | "der", 408 | "elliptic-curve", 409 | "rfc6979", 410 | "signature", 411 | ] 412 | 413 | [[package]] 414 | name = "either" 415 | version = "1.8.0" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" 418 | 419 | [[package]] 420 | name = "elliptic-curve" 421 | version = "0.12.3" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" 424 | dependencies = [ 425 | "base16ct", 426 | "crypto-bigint", 427 | "der", 428 | "digest", 429 | "ff", 430 | "generic-array", 431 | "group", 432 | "hkdf", 433 | "pem-rfc7468", 434 | "pkcs8", 435 | "rand_core", 436 | "sec1", 437 | "subtle", 438 | "zeroize", 439 | ] 440 | 441 | [[package]] 442 | name = "fallible-iterator" 443 | version = "0.2.0" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 446 | 447 | [[package]] 448 | name = "fallible-streaming-iterator" 449 | version = "0.1.9" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" 452 | 453 | [[package]] 454 | name = "fastrand" 455 | version = "1.8.0" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" 458 | dependencies = [ 459 | "instant", 460 | ] 461 | 462 | [[package]] 463 | name = "ff" 464 | version = "0.12.1" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" 467 | dependencies = [ 468 | "rand_core", 469 | "subtle", 470 | ] 471 | 472 | [[package]] 473 | name = "fixedbitset" 474 | version = "0.4.2" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" 477 | 478 | [[package]] 479 | name = "fnv" 480 | version = "1.0.7" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 483 | 484 | [[package]] 485 | name = "form_urlencoded" 486 | version = "1.1.0" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" 489 | dependencies = [ 490 | "percent-encoding", 491 | ] 492 | 493 | [[package]] 494 | name = "futures" 495 | version = "0.3.25" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" 498 | dependencies = [ 499 | "futures-channel", 500 | "futures-core", 501 | "futures-executor", 502 | "futures-io", 503 | "futures-sink", 504 | "futures-task", 505 | "futures-util", 506 | ] 507 | 508 | [[package]] 509 | name = "futures-channel" 510 | version = "0.3.25" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" 513 | dependencies = [ 514 | "futures-core", 515 | "futures-sink", 516 | ] 517 | 518 | [[package]] 519 | name = "futures-core" 520 | version = "0.3.25" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" 523 | 524 | [[package]] 525 | name = "futures-executor" 526 | version = "0.3.25" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" 529 | dependencies = [ 530 | "futures-core", 531 | "futures-task", 532 | "futures-util", 533 | ] 534 | 535 | [[package]] 536 | name = "futures-io" 537 | version = "0.3.25" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" 540 | 541 | [[package]] 542 | name = "futures-macro" 543 | version = "0.3.25" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" 546 | dependencies = [ 547 | "proc-macro2", 548 | "quote", 549 | "syn", 550 | ] 551 | 552 | [[package]] 553 | name = "futures-sink" 554 | version = "0.3.25" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" 557 | 558 | [[package]] 559 | name = "futures-task" 560 | version = "0.3.25" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" 563 | 564 | [[package]] 565 | name = "futures-util" 566 | version = "0.3.25" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" 569 | dependencies = [ 570 | "futures-channel", 571 | "futures-core", 572 | "futures-io", 573 | "futures-macro", 574 | "futures-sink", 575 | "futures-task", 576 | "memchr", 577 | "pin-project-lite", 578 | "pin-utils", 579 | "slab", 580 | ] 581 | 582 | [[package]] 583 | name = "generic-array" 584 | version = "0.14.6" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" 587 | dependencies = [ 588 | "typenum", 589 | "version_check", 590 | ] 591 | 592 | [[package]] 593 | name = "getrandom" 594 | version = "0.2.8" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 597 | dependencies = [ 598 | "cfg-if", 599 | "libc", 600 | "wasi 0.11.0+wasi-snapshot-preview1", 601 | ] 602 | 603 | [[package]] 604 | name = "group" 605 | version = "0.12.1" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" 608 | dependencies = [ 609 | "ff", 610 | "rand_core", 611 | "subtle", 612 | ] 613 | 614 | [[package]] 615 | name = "h2" 616 | version = "0.3.15" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" 619 | dependencies = [ 620 | "bytes", 621 | "fnv", 622 | "futures-core", 623 | "futures-sink", 624 | "futures-util", 625 | "http", 626 | "indexmap", 627 | "slab", 628 | "tokio", 629 | "tokio-util", 630 | "tracing", 631 | ] 632 | 633 | [[package]] 634 | name = "hashbrown" 635 | version = "0.12.3" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 638 | dependencies = [ 639 | "ahash", 640 | ] 641 | 642 | [[package]] 643 | name = "hashlink" 644 | version = "0.8.1" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa" 647 | dependencies = [ 648 | "hashbrown", 649 | ] 650 | 651 | [[package]] 652 | name = "heck" 653 | version = "0.4.0" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 656 | 657 | [[package]] 658 | name = "hermit-abi" 659 | version = "0.1.19" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 662 | dependencies = [ 663 | "libc", 664 | ] 665 | 666 | [[package]] 667 | name = "hkdf" 668 | version = "0.12.3" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" 671 | dependencies = [ 672 | "hmac", 673 | ] 674 | 675 | [[package]] 676 | name = "hmac" 677 | version = "0.12.1" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 680 | dependencies = [ 681 | "digest", 682 | ] 683 | 684 | [[package]] 685 | name = "http" 686 | version = "0.2.8" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" 689 | dependencies = [ 690 | "bytes", 691 | "fnv", 692 | "itoa", 693 | ] 694 | 695 | [[package]] 696 | name = "http-body" 697 | version = "0.4.5" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" 700 | dependencies = [ 701 | "bytes", 702 | "http", 703 | "pin-project-lite", 704 | ] 705 | 706 | [[package]] 707 | name = "http-range-header" 708 | version = "0.3.0" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" 711 | 712 | [[package]] 713 | name = "httparse" 714 | version = "1.8.0" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 717 | 718 | [[package]] 719 | name = "httpdate" 720 | version = "1.0.2" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 723 | 724 | [[package]] 725 | name = "hyper" 726 | version = "0.14.23" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" 729 | dependencies = [ 730 | "bytes", 731 | "futures-channel", 732 | "futures-core", 733 | "futures-util", 734 | "h2", 735 | "http", 736 | "http-body", 737 | "httparse", 738 | "httpdate", 739 | "itoa", 740 | "pin-project-lite", 741 | "socket2", 742 | "tokio", 743 | "tower-service", 744 | "tracing", 745 | "want", 746 | ] 747 | 748 | [[package]] 749 | name = "hyper-timeout" 750 | version = "0.4.1" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" 753 | dependencies = [ 754 | "hyper", 755 | "pin-project-lite", 756 | "tokio", 757 | "tokio-io-timeout", 758 | ] 759 | 760 | [[package]] 761 | name = "iana-time-zone" 762 | version = "0.1.53" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" 765 | dependencies = [ 766 | "android_system_properties", 767 | "core-foundation-sys", 768 | "iana-time-zone-haiku", 769 | "js-sys", 770 | "wasm-bindgen", 771 | "winapi", 772 | ] 773 | 774 | [[package]] 775 | name = "iana-time-zone-haiku" 776 | version = "0.1.1" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" 779 | dependencies = [ 780 | "cxx", 781 | "cxx-build", 782 | ] 783 | 784 | [[package]] 785 | name = "idna" 786 | version = "0.3.0" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" 789 | dependencies = [ 790 | "unicode-bidi", 791 | "unicode-normalization", 792 | ] 793 | 794 | [[package]] 795 | name = "indexmap" 796 | version = "1.9.2" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" 799 | dependencies = [ 800 | "autocfg", 801 | "hashbrown", 802 | ] 803 | 804 | [[package]] 805 | name = "inout" 806 | version = "0.1.3" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" 809 | dependencies = [ 810 | "generic-array", 811 | ] 812 | 813 | [[package]] 814 | name = "instant" 815 | version = "0.1.12" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 818 | dependencies = [ 819 | "cfg-if", 820 | ] 821 | 822 | [[package]] 823 | name = "itertools" 824 | version = "0.10.5" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 827 | dependencies = [ 828 | "either", 829 | ] 830 | 831 | [[package]] 832 | name = "itoa" 833 | version = "1.0.4" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" 836 | 837 | [[package]] 838 | name = "js-sys" 839 | version = "0.3.60" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" 842 | dependencies = [ 843 | "wasm-bindgen", 844 | ] 845 | 846 | [[package]] 847 | name = "jsonwebtoken" 848 | version = "8.1.1" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "1aa4b4af834c6cfd35d8763d359661b90f2e45d8f750a0849156c7f4671af09c" 851 | dependencies = [ 852 | "base64", 853 | "pem", 854 | "ring", 855 | "serde", 856 | "serde_json", 857 | "simple_asn1", 858 | ] 859 | 860 | [[package]] 861 | name = "lazy_static" 862 | version = "1.4.0" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 865 | 866 | [[package]] 867 | name = "libc" 868 | version = "0.2.137" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" 871 | 872 | [[package]] 873 | name = "libsqlite3-sys" 874 | version = "0.25.2" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa" 877 | dependencies = [ 878 | "pkg-config", 879 | "vcpkg", 880 | ] 881 | 882 | [[package]] 883 | name = "limit-am" 884 | version = "0.1.0" 885 | dependencies = [ 886 | "limit-deps", 887 | ] 888 | 889 | [[package]] 890 | name = "limit-config" 891 | version = "0.1.0" 892 | dependencies = [ 893 | "limit-deps", 894 | ] 895 | 896 | [[package]] 897 | name = "limit-db" 898 | version = "0.1.0" 899 | dependencies = [ 900 | "limit-config", 901 | "limit-deps", 902 | ] 903 | 904 | [[package]] 905 | name = "limit-deps" 906 | version = "0.1.0" 907 | dependencies = [ 908 | "aes", 909 | "anyhow", 910 | "async-trait", 911 | "base64", 912 | "chrono", 913 | "crossbeam-channel", 914 | "diesel", 915 | "elliptic-curve", 916 | "futures", 917 | "hyper", 918 | "jsonwebtoken", 919 | "metrics", 920 | "mod_use", 921 | "once_cell", 922 | "p256", 923 | "prost", 924 | "r2d2", 925 | "r2d2_sqlite", 926 | "rand", 927 | "redis", 928 | "serde", 929 | "serde_json", 930 | "tokio", 931 | "tokio-util", 932 | "toml", 933 | "tonic", 934 | "tower", 935 | "tracing", 936 | "tracing-subscriber", 937 | "url", 938 | "uuid", 939 | ] 940 | 941 | [[package]] 942 | name = "limit-server" 943 | version = "0.1.0" 944 | dependencies = [ 945 | "limit-deps", 946 | "limit-server-auth-test", 947 | "limit-server-event-test", 948 | "limit-test-utils", 949 | ] 950 | 951 | [[package]] 952 | name = "limit-server-auth" 953 | version = "0.1.0" 954 | dependencies = [ 955 | "limit-am", 956 | "limit-config", 957 | "limit-db", 958 | "limit-deps", 959 | "limit-test-utils", 960 | "limit-utils", 961 | "tonic-gen", 962 | ] 963 | 964 | [[package]] 965 | name = "limit-server-auth-test" 966 | version = "0.1.0" 967 | dependencies = [ 968 | "limit-am", 969 | "limit-config", 970 | "limit-db", 971 | "limit-deps", 972 | "limit-server-auth", 973 | "limit-test-utils", 974 | ] 975 | 976 | [[package]] 977 | name = "limit-server-event" 978 | version = "0.1.0" 979 | dependencies = [ 980 | "limit-am", 981 | "limit-config", 982 | "limit-db", 983 | "limit-deps", 984 | "limit-server-auth", 985 | "limit-utils", 986 | "tonic-gen", 987 | ] 988 | 989 | [[package]] 990 | name = "limit-server-event-test" 991 | version = "0.1.0" 992 | dependencies = [ 993 | "limit-am", 994 | "limit-config", 995 | "limit-db", 996 | "limit-deps", 997 | "limit-server-auth", 998 | "limit-server-event", 999 | "limit-test-utils", 1000 | "limit-utils", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "limit-server-subs" 1005 | version = "0.1.0" 1006 | dependencies = [ 1007 | "limit-am", 1008 | "limit-config", 1009 | "limit-db", 1010 | "limit-deps", 1011 | "limit-server-auth", 1012 | "limit-test-utils", 1013 | "limit-utils", 1014 | "tonic-gen", 1015 | ] 1016 | 1017 | [[package]] 1018 | name = "limit-test-utils" 1019 | version = "0.1.0" 1020 | dependencies = [ 1021 | "limit-am", 1022 | "limit-config", 1023 | "limit-db", 1024 | "limit-deps", 1025 | "limit-server-auth", 1026 | ] 1027 | 1028 | [[package]] 1029 | name = "limit-utils" 1030 | version = "0.1.0" 1031 | dependencies = [ 1032 | "limit-deps", 1033 | ] 1034 | 1035 | [[package]] 1036 | name = "link-cplusplus" 1037 | version = "1.0.7" 1038 | source = "registry+https://github.com/rust-lang/crates.io-index" 1039 | checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" 1040 | dependencies = [ 1041 | "cc", 1042 | ] 1043 | 1044 | [[package]] 1045 | name = "lock_api" 1046 | version = "0.4.9" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 1049 | dependencies = [ 1050 | "autocfg", 1051 | "scopeguard", 1052 | ] 1053 | 1054 | [[package]] 1055 | name = "log" 1056 | version = "0.4.17" 1057 | source = "registry+https://github.com/rust-lang/crates.io-index" 1058 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 1059 | dependencies = [ 1060 | "cfg-if", 1061 | ] 1062 | 1063 | [[package]] 1064 | name = "matchit" 1065 | version = "0.7.0" 1066 | source = "registry+https://github.com/rust-lang/crates.io-index" 1067 | checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" 1068 | 1069 | [[package]] 1070 | name = "memchr" 1071 | version = "2.5.0" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 1074 | 1075 | [[package]] 1076 | name = "metrics" 1077 | version = "0.20.1" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "7b9b8653cec6897f73b519a43fba5ee3d50f62fe9af80b428accdcc093b4a849" 1080 | dependencies = [ 1081 | "ahash", 1082 | "metrics-macros", 1083 | "portable-atomic", 1084 | ] 1085 | 1086 | [[package]] 1087 | name = "metrics-macros" 1088 | version = "0.6.0" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "731f8ecebd9f3a4aa847dfe75455e4757a45da40a7793d2f0b1f9b6ed18b23f3" 1091 | dependencies = [ 1092 | "proc-macro2", 1093 | "quote", 1094 | "syn", 1095 | ] 1096 | 1097 | [[package]] 1098 | name = "mime" 1099 | version = "0.3.16" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 1102 | 1103 | [[package]] 1104 | name = "mio" 1105 | version = "0.8.5" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" 1108 | dependencies = [ 1109 | "libc", 1110 | "log", 1111 | "wasi 0.11.0+wasi-snapshot-preview1", 1112 | "windows-sys", 1113 | ] 1114 | 1115 | [[package]] 1116 | name = "mod_use" 1117 | version = "0.2.1" 1118 | source = "registry+https://github.com/rust-lang/crates.io-index" 1119 | checksum = "d95ee98a292cf91c2f5b3f35424773af16842a68b3be33b389137606b2633539" 1120 | 1121 | [[package]] 1122 | name = "multimap" 1123 | version = "0.8.3" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" 1126 | 1127 | [[package]] 1128 | name = "nu-ansi-term" 1129 | version = "0.46.0" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 1132 | dependencies = [ 1133 | "overload", 1134 | "winapi", 1135 | ] 1136 | 1137 | [[package]] 1138 | name = "num-bigint" 1139 | version = "0.4.3" 1140 | source = "registry+https://github.com/rust-lang/crates.io-index" 1141 | checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" 1142 | dependencies = [ 1143 | "autocfg", 1144 | "num-integer", 1145 | "num-traits", 1146 | ] 1147 | 1148 | [[package]] 1149 | name = "num-integer" 1150 | version = "0.1.45" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 1153 | dependencies = [ 1154 | "autocfg", 1155 | "num-traits", 1156 | ] 1157 | 1158 | [[package]] 1159 | name = "num-traits" 1160 | version = "0.2.15" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 1163 | dependencies = [ 1164 | "autocfg", 1165 | ] 1166 | 1167 | [[package]] 1168 | name = "num_cpus" 1169 | version = "1.14.0" 1170 | source = "registry+https://github.com/rust-lang/crates.io-index" 1171 | checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" 1172 | dependencies = [ 1173 | "hermit-abi", 1174 | "libc", 1175 | ] 1176 | 1177 | [[package]] 1178 | name = "once_cell" 1179 | version = "1.16.0" 1180 | source = "registry+https://github.com/rust-lang/crates.io-index" 1181 | checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" 1182 | 1183 | [[package]] 1184 | name = "overload" 1185 | version = "0.1.1" 1186 | source = "registry+https://github.com/rust-lang/crates.io-index" 1187 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 1188 | 1189 | [[package]] 1190 | name = "p256" 1191 | version = "0.11.1" 1192 | source = "registry+https://github.com/rust-lang/crates.io-index" 1193 | checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" 1194 | dependencies = [ 1195 | "ecdsa", 1196 | "elliptic-curve", 1197 | "sha2", 1198 | ] 1199 | 1200 | [[package]] 1201 | name = "parking_lot" 1202 | version = "0.12.1" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 1205 | dependencies = [ 1206 | "lock_api", 1207 | "parking_lot_core", 1208 | ] 1209 | 1210 | [[package]] 1211 | name = "parking_lot_core" 1212 | version = "0.9.4" 1213 | source = "registry+https://github.com/rust-lang/crates.io-index" 1214 | checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" 1215 | dependencies = [ 1216 | "cfg-if", 1217 | "libc", 1218 | "redox_syscall", 1219 | "smallvec", 1220 | "windows-sys", 1221 | ] 1222 | 1223 | [[package]] 1224 | name = "pem" 1225 | version = "1.1.0" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4" 1228 | dependencies = [ 1229 | "base64", 1230 | ] 1231 | 1232 | [[package]] 1233 | name = "pem-rfc7468" 1234 | version = "0.6.0" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac" 1237 | dependencies = [ 1238 | "base64ct", 1239 | ] 1240 | 1241 | [[package]] 1242 | name = "percent-encoding" 1243 | version = "2.2.0" 1244 | source = "registry+https://github.com/rust-lang/crates.io-index" 1245 | checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" 1246 | 1247 | [[package]] 1248 | name = "petgraph" 1249 | version = "0.6.2" 1250 | source = "registry+https://github.com/rust-lang/crates.io-index" 1251 | checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" 1252 | dependencies = [ 1253 | "fixedbitset", 1254 | "indexmap", 1255 | ] 1256 | 1257 | [[package]] 1258 | name = "pin-project" 1259 | version = "1.0.12" 1260 | source = "registry+https://github.com/rust-lang/crates.io-index" 1261 | checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" 1262 | dependencies = [ 1263 | "pin-project-internal", 1264 | ] 1265 | 1266 | [[package]] 1267 | name = "pin-project-internal" 1268 | version = "1.0.12" 1269 | source = "registry+https://github.com/rust-lang/crates.io-index" 1270 | checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" 1271 | dependencies = [ 1272 | "proc-macro2", 1273 | "quote", 1274 | "syn", 1275 | ] 1276 | 1277 | [[package]] 1278 | name = "pin-project-lite" 1279 | version = "0.2.9" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 1282 | 1283 | [[package]] 1284 | name = "pin-utils" 1285 | version = "0.1.0" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1288 | 1289 | [[package]] 1290 | name = "pkcs8" 1291 | version = "0.9.0" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" 1294 | dependencies = [ 1295 | "der", 1296 | "spki", 1297 | ] 1298 | 1299 | [[package]] 1300 | name = "pkg-config" 1301 | version = "0.3.26" 1302 | source = "registry+https://github.com/rust-lang/crates.io-index" 1303 | checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" 1304 | 1305 | [[package]] 1306 | name = "portable-atomic" 1307 | version = "0.3.15" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "15eb2c6e362923af47e13c23ca5afb859e83d54452c55b0b9ac763b8f7c1ac16" 1310 | 1311 | [[package]] 1312 | name = "ppv-lite86" 1313 | version = "0.2.17" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 1316 | 1317 | [[package]] 1318 | name = "prettyplease" 1319 | version = "0.1.21" 1320 | source = "registry+https://github.com/rust-lang/crates.io-index" 1321 | checksum = "c142c0e46b57171fe0c528bee8c5b7569e80f0c17e377cd0e30ea57dbc11bb51" 1322 | dependencies = [ 1323 | "proc-macro2", 1324 | "syn", 1325 | ] 1326 | 1327 | [[package]] 1328 | name = "proc-macro-error" 1329 | version = "1.0.4" 1330 | source = "registry+https://github.com/rust-lang/crates.io-index" 1331 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1332 | dependencies = [ 1333 | "proc-macro-error-attr", 1334 | "proc-macro2", 1335 | "quote", 1336 | "syn", 1337 | "version_check", 1338 | ] 1339 | 1340 | [[package]] 1341 | name = "proc-macro-error-attr" 1342 | version = "1.0.4" 1343 | source = "registry+https://github.com/rust-lang/crates.io-index" 1344 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1345 | dependencies = [ 1346 | "proc-macro2", 1347 | "quote", 1348 | "version_check", 1349 | ] 1350 | 1351 | [[package]] 1352 | name = "proc-macro2" 1353 | version = "1.0.47" 1354 | source = "registry+https://github.com/rust-lang/crates.io-index" 1355 | checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" 1356 | dependencies = [ 1357 | "unicode-ident", 1358 | ] 1359 | 1360 | [[package]] 1361 | name = "prost" 1362 | version = "0.11.2" 1363 | source = "registry+https://github.com/rust-lang/crates.io-index" 1364 | checksum = "a0841812012b2d4a6145fae9a6af1534873c32aa67fff26bd09f8fa42c83f95a" 1365 | dependencies = [ 1366 | "bytes", 1367 | "prost-derive", 1368 | ] 1369 | 1370 | [[package]] 1371 | name = "prost-build" 1372 | version = "0.11.3" 1373 | source = "registry+https://github.com/rust-lang/crates.io-index" 1374 | checksum = "e330bf1316db56b12c2bcfa399e8edddd4821965ea25ddb2c134b610b1c1c604" 1375 | dependencies = [ 1376 | "bytes", 1377 | "heck", 1378 | "itertools", 1379 | "lazy_static", 1380 | "log", 1381 | "multimap", 1382 | "petgraph", 1383 | "prettyplease", 1384 | "prost", 1385 | "prost-types", 1386 | "regex", 1387 | "syn", 1388 | "tempfile", 1389 | "which", 1390 | ] 1391 | 1392 | [[package]] 1393 | name = "prost-derive" 1394 | version = "0.11.2" 1395 | source = "registry+https://github.com/rust-lang/crates.io-index" 1396 | checksum = "164ae68b6587001ca506d3bf7f1000bfa248d0e1217b618108fba4ec1d0cc306" 1397 | dependencies = [ 1398 | "anyhow", 1399 | "itertools", 1400 | "proc-macro2", 1401 | "quote", 1402 | "syn", 1403 | ] 1404 | 1405 | [[package]] 1406 | name = "prost-types" 1407 | version = "0.11.2" 1408 | source = "registry+https://github.com/rust-lang/crates.io-index" 1409 | checksum = "747761bc3dc48f9a34553bf65605cf6cb6288ba219f3450b4275dbd81539551a" 1410 | dependencies = [ 1411 | "bytes", 1412 | "prost", 1413 | ] 1414 | 1415 | [[package]] 1416 | name = "quote" 1417 | version = "1.0.21" 1418 | source = "registry+https://github.com/rust-lang/crates.io-index" 1419 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 1420 | dependencies = [ 1421 | "proc-macro2", 1422 | ] 1423 | 1424 | [[package]] 1425 | name = "r2d2" 1426 | version = "0.8.10" 1427 | source = "registry+https://github.com/rust-lang/crates.io-index" 1428 | checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" 1429 | dependencies = [ 1430 | "log", 1431 | "parking_lot", 1432 | "scheduled-thread-pool", 1433 | ] 1434 | 1435 | [[package]] 1436 | name = "r2d2_sqlite" 1437 | version = "0.21.0" 1438 | source = "registry+https://github.com/rust-lang/crates.io-index" 1439 | checksum = "b4f5d0337e99cd5cacd91ffc326c6cc9d8078def459df560c4f9bf9ba4a51034" 1440 | dependencies = [ 1441 | "r2d2", 1442 | "rusqlite", 1443 | ] 1444 | 1445 | [[package]] 1446 | name = "rand" 1447 | version = "0.8.5" 1448 | source = "registry+https://github.com/rust-lang/crates.io-index" 1449 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1450 | dependencies = [ 1451 | "libc", 1452 | "rand_chacha", 1453 | "rand_core", 1454 | ] 1455 | 1456 | [[package]] 1457 | name = "rand_chacha" 1458 | version = "0.3.1" 1459 | source = "registry+https://github.com/rust-lang/crates.io-index" 1460 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1461 | dependencies = [ 1462 | "ppv-lite86", 1463 | "rand_core", 1464 | ] 1465 | 1466 | [[package]] 1467 | name = "rand_core" 1468 | version = "0.6.4" 1469 | source = "registry+https://github.com/rust-lang/crates.io-index" 1470 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1471 | dependencies = [ 1472 | "getrandom", 1473 | ] 1474 | 1475 | [[package]] 1476 | name = "redis" 1477 | version = "0.22.1" 1478 | source = "registry+https://github.com/rust-lang/crates.io-index" 1479 | checksum = "513b3649f1a111c17954296e4a3b9eecb108b766c803e2b99f179ebe27005985" 1480 | dependencies = [ 1481 | "async-trait", 1482 | "bytes", 1483 | "combine", 1484 | "crc16", 1485 | "futures-util", 1486 | "itoa", 1487 | "percent-encoding", 1488 | "pin-project-lite", 1489 | "r2d2", 1490 | "rand", 1491 | "ryu", 1492 | "sha1_smol", 1493 | "tokio", 1494 | "tokio-util", 1495 | "url", 1496 | ] 1497 | 1498 | [[package]] 1499 | name = "redox_syscall" 1500 | version = "0.2.16" 1501 | source = "registry+https://github.com/rust-lang/crates.io-index" 1502 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 1503 | dependencies = [ 1504 | "bitflags", 1505 | ] 1506 | 1507 | [[package]] 1508 | name = "regex" 1509 | version = "1.7.0" 1510 | source = "registry+https://github.com/rust-lang/crates.io-index" 1511 | checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" 1512 | dependencies = [ 1513 | "regex-syntax", 1514 | ] 1515 | 1516 | [[package]] 1517 | name = "regex-syntax" 1518 | version = "0.6.28" 1519 | source = "registry+https://github.com/rust-lang/crates.io-index" 1520 | checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 1521 | 1522 | [[package]] 1523 | name = "remove_dir_all" 1524 | version = "0.5.3" 1525 | source = "registry+https://github.com/rust-lang/crates.io-index" 1526 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 1527 | dependencies = [ 1528 | "winapi", 1529 | ] 1530 | 1531 | [[package]] 1532 | name = "rfc6979" 1533 | version = "0.3.1" 1534 | source = "registry+https://github.com/rust-lang/crates.io-index" 1535 | checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" 1536 | dependencies = [ 1537 | "crypto-bigint", 1538 | "hmac", 1539 | "zeroize", 1540 | ] 1541 | 1542 | [[package]] 1543 | name = "ring" 1544 | version = "0.16.20" 1545 | source = "registry+https://github.com/rust-lang/crates.io-index" 1546 | checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" 1547 | dependencies = [ 1548 | "cc", 1549 | "libc", 1550 | "once_cell", 1551 | "spin", 1552 | "untrusted", 1553 | "web-sys", 1554 | "winapi", 1555 | ] 1556 | 1557 | [[package]] 1558 | name = "rusqlite" 1559 | version = "0.28.0" 1560 | source = "registry+https://github.com/rust-lang/crates.io-index" 1561 | checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" 1562 | dependencies = [ 1563 | "bitflags", 1564 | "fallible-iterator", 1565 | "fallible-streaming-iterator", 1566 | "hashlink", 1567 | "libsqlite3-sys", 1568 | "smallvec", 1569 | ] 1570 | 1571 | [[package]] 1572 | name = "rustversion" 1573 | version = "1.0.9" 1574 | source = "registry+https://github.com/rust-lang/crates.io-index" 1575 | checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" 1576 | 1577 | [[package]] 1578 | name = "ryu" 1579 | version = "1.0.11" 1580 | source = "registry+https://github.com/rust-lang/crates.io-index" 1581 | checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 1582 | 1583 | [[package]] 1584 | name = "scheduled-thread-pool" 1585 | version = "0.2.6" 1586 | source = "registry+https://github.com/rust-lang/crates.io-index" 1587 | checksum = "977a7519bff143a44f842fd07e80ad1329295bd71686457f18e496736f4bf9bf" 1588 | dependencies = [ 1589 | "parking_lot", 1590 | ] 1591 | 1592 | [[package]] 1593 | name = "scopeguard" 1594 | version = "1.1.0" 1595 | source = "registry+https://github.com/rust-lang/crates.io-index" 1596 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1597 | 1598 | [[package]] 1599 | name = "scratch" 1600 | version = "1.0.2" 1601 | source = "registry+https://github.com/rust-lang/crates.io-index" 1602 | checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" 1603 | 1604 | [[package]] 1605 | name = "sec1" 1606 | version = "0.3.0" 1607 | source = "registry+https://github.com/rust-lang/crates.io-index" 1608 | checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" 1609 | dependencies = [ 1610 | "base16ct", 1611 | "der", 1612 | "generic-array", 1613 | "pkcs8", 1614 | "subtle", 1615 | "zeroize", 1616 | ] 1617 | 1618 | [[package]] 1619 | name = "serde" 1620 | version = "1.0.147" 1621 | source = "registry+https://github.com/rust-lang/crates.io-index" 1622 | checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" 1623 | dependencies = [ 1624 | "serde_derive", 1625 | ] 1626 | 1627 | [[package]] 1628 | name = "serde_derive" 1629 | version = "1.0.147" 1630 | source = "registry+https://github.com/rust-lang/crates.io-index" 1631 | checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" 1632 | dependencies = [ 1633 | "proc-macro2", 1634 | "quote", 1635 | "syn", 1636 | ] 1637 | 1638 | [[package]] 1639 | name = "serde_json" 1640 | version = "1.0.89" 1641 | source = "registry+https://github.com/rust-lang/crates.io-index" 1642 | checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" 1643 | dependencies = [ 1644 | "itoa", 1645 | "ryu", 1646 | "serde", 1647 | ] 1648 | 1649 | [[package]] 1650 | name = "sha1_smol" 1651 | version = "1.0.0" 1652 | source = "registry+https://github.com/rust-lang/crates.io-index" 1653 | checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" 1654 | 1655 | [[package]] 1656 | name = "sha2" 1657 | version = "0.10.6" 1658 | source = "registry+https://github.com/rust-lang/crates.io-index" 1659 | checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" 1660 | dependencies = [ 1661 | "cfg-if", 1662 | "cpufeatures", 1663 | "digest", 1664 | ] 1665 | 1666 | [[package]] 1667 | name = "sharded-slab" 1668 | version = "0.1.4" 1669 | source = "registry+https://github.com/rust-lang/crates.io-index" 1670 | checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 1671 | dependencies = [ 1672 | "lazy_static", 1673 | ] 1674 | 1675 | [[package]] 1676 | name = "signal-hook-registry" 1677 | version = "1.4.0" 1678 | source = "registry+https://github.com/rust-lang/crates.io-index" 1679 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 1680 | dependencies = [ 1681 | "libc", 1682 | ] 1683 | 1684 | [[package]] 1685 | name = "signature" 1686 | version = "1.6.4" 1687 | source = "registry+https://github.com/rust-lang/crates.io-index" 1688 | checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" 1689 | dependencies = [ 1690 | "digest", 1691 | "rand_core", 1692 | ] 1693 | 1694 | [[package]] 1695 | name = "simple_asn1" 1696 | version = "0.6.2" 1697 | source = "registry+https://github.com/rust-lang/crates.io-index" 1698 | checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" 1699 | dependencies = [ 1700 | "num-bigint", 1701 | "num-traits", 1702 | "thiserror", 1703 | "time 0.3.17", 1704 | ] 1705 | 1706 | [[package]] 1707 | name = "slab" 1708 | version = "0.4.7" 1709 | source = "registry+https://github.com/rust-lang/crates.io-index" 1710 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 1711 | dependencies = [ 1712 | "autocfg", 1713 | ] 1714 | 1715 | [[package]] 1716 | name = "smallvec" 1717 | version = "1.10.0" 1718 | source = "registry+https://github.com/rust-lang/crates.io-index" 1719 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 1720 | 1721 | [[package]] 1722 | name = "socket2" 1723 | version = "0.4.7" 1724 | source = "registry+https://github.com/rust-lang/crates.io-index" 1725 | checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" 1726 | dependencies = [ 1727 | "libc", 1728 | "winapi", 1729 | ] 1730 | 1731 | [[package]] 1732 | name = "spin" 1733 | version = "0.5.2" 1734 | source = "registry+https://github.com/rust-lang/crates.io-index" 1735 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 1736 | 1737 | [[package]] 1738 | name = "spki" 1739 | version = "0.6.0" 1740 | source = "registry+https://github.com/rust-lang/crates.io-index" 1741 | checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" 1742 | dependencies = [ 1743 | "base64ct", 1744 | "der", 1745 | ] 1746 | 1747 | [[package]] 1748 | name = "subtle" 1749 | version = "2.4.1" 1750 | source = "registry+https://github.com/rust-lang/crates.io-index" 1751 | checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" 1752 | 1753 | [[package]] 1754 | name = "syn" 1755 | version = "1.0.103" 1756 | source = "registry+https://github.com/rust-lang/crates.io-index" 1757 | checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" 1758 | dependencies = [ 1759 | "proc-macro2", 1760 | "quote", 1761 | "unicode-ident", 1762 | ] 1763 | 1764 | [[package]] 1765 | name = "sync_wrapper" 1766 | version = "0.1.1" 1767 | source = "registry+https://github.com/rust-lang/crates.io-index" 1768 | checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" 1769 | 1770 | [[package]] 1771 | name = "tempfile" 1772 | version = "3.3.0" 1773 | source = "registry+https://github.com/rust-lang/crates.io-index" 1774 | checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" 1775 | dependencies = [ 1776 | "cfg-if", 1777 | "fastrand", 1778 | "libc", 1779 | "redox_syscall", 1780 | "remove_dir_all", 1781 | "winapi", 1782 | ] 1783 | 1784 | [[package]] 1785 | name = "termcolor" 1786 | version = "1.1.3" 1787 | source = "registry+https://github.com/rust-lang/crates.io-index" 1788 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 1789 | dependencies = [ 1790 | "winapi-util", 1791 | ] 1792 | 1793 | [[package]] 1794 | name = "thiserror" 1795 | version = "1.0.37" 1796 | source = "registry+https://github.com/rust-lang/crates.io-index" 1797 | checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" 1798 | dependencies = [ 1799 | "thiserror-impl", 1800 | ] 1801 | 1802 | [[package]] 1803 | name = "thiserror-impl" 1804 | version = "1.0.37" 1805 | source = "registry+https://github.com/rust-lang/crates.io-index" 1806 | checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" 1807 | dependencies = [ 1808 | "proc-macro2", 1809 | "quote", 1810 | "syn", 1811 | ] 1812 | 1813 | [[package]] 1814 | name = "thread_local" 1815 | version = "1.1.4" 1816 | source = "registry+https://github.com/rust-lang/crates.io-index" 1817 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 1818 | dependencies = [ 1819 | "once_cell", 1820 | ] 1821 | 1822 | [[package]] 1823 | name = "time" 1824 | version = "0.1.44" 1825 | source = "registry+https://github.com/rust-lang/crates.io-index" 1826 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 1827 | dependencies = [ 1828 | "libc", 1829 | "wasi 0.10.0+wasi-snapshot-preview1", 1830 | "winapi", 1831 | ] 1832 | 1833 | [[package]] 1834 | name = "time" 1835 | version = "0.3.17" 1836 | source = "registry+https://github.com/rust-lang/crates.io-index" 1837 | checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" 1838 | dependencies = [ 1839 | "itoa", 1840 | "serde", 1841 | "time-core", 1842 | "time-macros", 1843 | ] 1844 | 1845 | [[package]] 1846 | name = "time-core" 1847 | version = "0.1.0" 1848 | source = "registry+https://github.com/rust-lang/crates.io-index" 1849 | checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" 1850 | 1851 | [[package]] 1852 | name = "time-macros" 1853 | version = "0.2.6" 1854 | source = "registry+https://github.com/rust-lang/crates.io-index" 1855 | checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" 1856 | dependencies = [ 1857 | "time-core", 1858 | ] 1859 | 1860 | [[package]] 1861 | name = "tinyvec" 1862 | version = "1.6.0" 1863 | source = "registry+https://github.com/rust-lang/crates.io-index" 1864 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1865 | dependencies = [ 1866 | "tinyvec_macros", 1867 | ] 1868 | 1869 | [[package]] 1870 | name = "tinyvec_macros" 1871 | version = "0.1.0" 1872 | source = "registry+https://github.com/rust-lang/crates.io-index" 1873 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1874 | 1875 | [[package]] 1876 | name = "tokio" 1877 | version = "1.22.0" 1878 | source = "registry+https://github.com/rust-lang/crates.io-index" 1879 | checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" 1880 | dependencies = [ 1881 | "autocfg", 1882 | "bytes", 1883 | "libc", 1884 | "memchr", 1885 | "mio", 1886 | "num_cpus", 1887 | "parking_lot", 1888 | "pin-project-lite", 1889 | "signal-hook-registry", 1890 | "socket2", 1891 | "tokio-macros", 1892 | "winapi", 1893 | ] 1894 | 1895 | [[package]] 1896 | name = "tokio-io-timeout" 1897 | version = "1.2.0" 1898 | source = "registry+https://github.com/rust-lang/crates.io-index" 1899 | checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" 1900 | dependencies = [ 1901 | "pin-project-lite", 1902 | "tokio", 1903 | ] 1904 | 1905 | [[package]] 1906 | name = "tokio-macros" 1907 | version = "1.8.0" 1908 | source = "registry+https://github.com/rust-lang/crates.io-index" 1909 | checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" 1910 | dependencies = [ 1911 | "proc-macro2", 1912 | "quote", 1913 | "syn", 1914 | ] 1915 | 1916 | [[package]] 1917 | name = "tokio-stream" 1918 | version = "0.1.11" 1919 | source = "registry+https://github.com/rust-lang/crates.io-index" 1920 | checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" 1921 | dependencies = [ 1922 | "futures-core", 1923 | "pin-project-lite", 1924 | "tokio", 1925 | ] 1926 | 1927 | [[package]] 1928 | name = "tokio-util" 1929 | version = "0.7.4" 1930 | source = "registry+https://github.com/rust-lang/crates.io-index" 1931 | checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" 1932 | dependencies = [ 1933 | "bytes", 1934 | "futures-core", 1935 | "futures-sink", 1936 | "pin-project-lite", 1937 | "tokio", 1938 | "tracing", 1939 | ] 1940 | 1941 | [[package]] 1942 | name = "toml" 1943 | version = "0.5.9" 1944 | source = "registry+https://github.com/rust-lang/crates.io-index" 1945 | checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" 1946 | dependencies = [ 1947 | "serde", 1948 | ] 1949 | 1950 | [[package]] 1951 | name = "tonic" 1952 | version = "0.8.3" 1953 | source = "registry+https://github.com/rust-lang/crates.io-index" 1954 | checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" 1955 | dependencies = [ 1956 | "async-stream", 1957 | "async-trait", 1958 | "axum", 1959 | "base64", 1960 | "bytes", 1961 | "futures-core", 1962 | "futures-util", 1963 | "h2", 1964 | "http", 1965 | "http-body", 1966 | "hyper", 1967 | "hyper-timeout", 1968 | "percent-encoding", 1969 | "pin-project", 1970 | "prost", 1971 | "prost-derive", 1972 | "tokio", 1973 | "tokio-stream", 1974 | "tokio-util", 1975 | "tower", 1976 | "tower-layer", 1977 | "tower-service", 1978 | "tracing", 1979 | "tracing-futures", 1980 | ] 1981 | 1982 | [[package]] 1983 | name = "tonic-build" 1984 | version = "0.8.4" 1985 | source = "registry+https://github.com/rust-lang/crates.io-index" 1986 | checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" 1987 | dependencies = [ 1988 | "prettyplease", 1989 | "proc-macro2", 1990 | "prost-build", 1991 | "quote", 1992 | "syn", 1993 | ] 1994 | 1995 | [[package]] 1996 | name = "tonic-gen" 1997 | version = "0.1.0" 1998 | dependencies = [ 1999 | "prost", 2000 | "tonic", 2001 | "tonic-build", 2002 | ] 2003 | 2004 | [[package]] 2005 | name = "tower" 2006 | version = "0.4.13" 2007 | source = "registry+https://github.com/rust-lang/crates.io-index" 2008 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 2009 | dependencies = [ 2010 | "futures-core", 2011 | "futures-util", 2012 | "indexmap", 2013 | "pin-project", 2014 | "pin-project-lite", 2015 | "rand", 2016 | "slab", 2017 | "tokio", 2018 | "tokio-util", 2019 | "tower-layer", 2020 | "tower-service", 2021 | "tracing", 2022 | ] 2023 | 2024 | [[package]] 2025 | name = "tower-http" 2026 | version = "0.3.4" 2027 | source = "registry+https://github.com/rust-lang/crates.io-index" 2028 | checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" 2029 | dependencies = [ 2030 | "bitflags", 2031 | "bytes", 2032 | "futures-core", 2033 | "futures-util", 2034 | "http", 2035 | "http-body", 2036 | "http-range-header", 2037 | "pin-project-lite", 2038 | "tower", 2039 | "tower-layer", 2040 | "tower-service", 2041 | ] 2042 | 2043 | [[package]] 2044 | name = "tower-layer" 2045 | version = "0.3.2" 2046 | source = "registry+https://github.com/rust-lang/crates.io-index" 2047 | checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" 2048 | 2049 | [[package]] 2050 | name = "tower-service" 2051 | version = "0.3.2" 2052 | source = "registry+https://github.com/rust-lang/crates.io-index" 2053 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 2054 | 2055 | [[package]] 2056 | name = "tracing" 2057 | version = "0.1.37" 2058 | source = "registry+https://github.com/rust-lang/crates.io-index" 2059 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 2060 | dependencies = [ 2061 | "cfg-if", 2062 | "log", 2063 | "pin-project-lite", 2064 | "tracing-attributes", 2065 | "tracing-core", 2066 | ] 2067 | 2068 | [[package]] 2069 | name = "tracing-attributes" 2070 | version = "0.1.23" 2071 | source = "registry+https://github.com/rust-lang/crates.io-index" 2072 | checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" 2073 | dependencies = [ 2074 | "proc-macro2", 2075 | "quote", 2076 | "syn", 2077 | ] 2078 | 2079 | [[package]] 2080 | name = "tracing-core" 2081 | version = "0.1.30" 2082 | source = "registry+https://github.com/rust-lang/crates.io-index" 2083 | checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" 2084 | dependencies = [ 2085 | "once_cell", 2086 | "valuable", 2087 | ] 2088 | 2089 | [[package]] 2090 | name = "tracing-futures" 2091 | version = "0.2.5" 2092 | source = "registry+https://github.com/rust-lang/crates.io-index" 2093 | checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" 2094 | dependencies = [ 2095 | "pin-project", 2096 | "tracing", 2097 | ] 2098 | 2099 | [[package]] 2100 | name = "tracing-log" 2101 | version = "0.1.3" 2102 | source = "registry+https://github.com/rust-lang/crates.io-index" 2103 | checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" 2104 | dependencies = [ 2105 | "lazy_static", 2106 | "log", 2107 | "tracing-core", 2108 | ] 2109 | 2110 | [[package]] 2111 | name = "tracing-subscriber" 2112 | version = "0.3.16" 2113 | source = "registry+https://github.com/rust-lang/crates.io-index" 2114 | checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" 2115 | dependencies = [ 2116 | "nu-ansi-term", 2117 | "sharded-slab", 2118 | "smallvec", 2119 | "thread_local", 2120 | "tracing-core", 2121 | "tracing-log", 2122 | ] 2123 | 2124 | [[package]] 2125 | name = "try-lock" 2126 | version = "0.2.3" 2127 | source = "registry+https://github.com/rust-lang/crates.io-index" 2128 | checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 2129 | 2130 | [[package]] 2131 | name = "typenum" 2132 | version = "1.15.0" 2133 | source = "registry+https://github.com/rust-lang/crates.io-index" 2134 | checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" 2135 | 2136 | [[package]] 2137 | name = "unicode-bidi" 2138 | version = "0.3.8" 2139 | source = "registry+https://github.com/rust-lang/crates.io-index" 2140 | checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" 2141 | 2142 | [[package]] 2143 | name = "unicode-ident" 2144 | version = "1.0.5" 2145 | source = "registry+https://github.com/rust-lang/crates.io-index" 2146 | checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" 2147 | 2148 | [[package]] 2149 | name = "unicode-normalization" 2150 | version = "0.1.22" 2151 | source = "registry+https://github.com/rust-lang/crates.io-index" 2152 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 2153 | dependencies = [ 2154 | "tinyvec", 2155 | ] 2156 | 2157 | [[package]] 2158 | name = "unicode-width" 2159 | version = "0.1.10" 2160 | source = "registry+https://github.com/rust-lang/crates.io-index" 2161 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 2162 | 2163 | [[package]] 2164 | name = "untrusted" 2165 | version = "0.7.1" 2166 | source = "registry+https://github.com/rust-lang/crates.io-index" 2167 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 2168 | 2169 | [[package]] 2170 | name = "url" 2171 | version = "2.3.1" 2172 | source = "registry+https://github.com/rust-lang/crates.io-index" 2173 | checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" 2174 | dependencies = [ 2175 | "form_urlencoded", 2176 | "idna", 2177 | "percent-encoding", 2178 | "serde", 2179 | ] 2180 | 2181 | [[package]] 2182 | name = "uuid" 2183 | version = "1.2.2" 2184 | source = "registry+https://github.com/rust-lang/crates.io-index" 2185 | checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" 2186 | dependencies = [ 2187 | "atomic", 2188 | "getrandom", 2189 | "rand", 2190 | "serde", 2191 | "uuid-macro-internal", 2192 | ] 2193 | 2194 | [[package]] 2195 | name = "uuid-macro-internal" 2196 | version = "1.2.2" 2197 | source = "registry+https://github.com/rust-lang/crates.io-index" 2198 | checksum = "73bc89f2894593e665241e0052c3791999e6787b7c4831daa0a5c2e637e276d8" 2199 | dependencies = [ 2200 | "proc-macro2", 2201 | "quote", 2202 | "syn", 2203 | ] 2204 | 2205 | [[package]] 2206 | name = "valuable" 2207 | version = "0.1.0" 2208 | source = "registry+https://github.com/rust-lang/crates.io-index" 2209 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 2210 | 2211 | [[package]] 2212 | name = "vcpkg" 2213 | version = "0.2.15" 2214 | source = "registry+https://github.com/rust-lang/crates.io-index" 2215 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2216 | 2217 | [[package]] 2218 | name = "version_check" 2219 | version = "0.9.4" 2220 | source = "registry+https://github.com/rust-lang/crates.io-index" 2221 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 2222 | 2223 | [[package]] 2224 | name = "want" 2225 | version = "0.3.0" 2226 | source = "registry+https://github.com/rust-lang/crates.io-index" 2227 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 2228 | dependencies = [ 2229 | "log", 2230 | "try-lock", 2231 | ] 2232 | 2233 | [[package]] 2234 | name = "wasi" 2235 | version = "0.10.0+wasi-snapshot-preview1" 2236 | source = "registry+https://github.com/rust-lang/crates.io-index" 2237 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 2238 | 2239 | [[package]] 2240 | name = "wasi" 2241 | version = "0.11.0+wasi-snapshot-preview1" 2242 | source = "registry+https://github.com/rust-lang/crates.io-index" 2243 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2244 | 2245 | [[package]] 2246 | name = "wasm-bindgen" 2247 | version = "0.2.83" 2248 | source = "registry+https://github.com/rust-lang/crates.io-index" 2249 | checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" 2250 | dependencies = [ 2251 | "cfg-if", 2252 | "wasm-bindgen-macro", 2253 | ] 2254 | 2255 | [[package]] 2256 | name = "wasm-bindgen-backend" 2257 | version = "0.2.83" 2258 | source = "registry+https://github.com/rust-lang/crates.io-index" 2259 | checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" 2260 | dependencies = [ 2261 | "bumpalo", 2262 | "log", 2263 | "once_cell", 2264 | "proc-macro2", 2265 | "quote", 2266 | "syn", 2267 | "wasm-bindgen-shared", 2268 | ] 2269 | 2270 | [[package]] 2271 | name = "wasm-bindgen-macro" 2272 | version = "0.2.83" 2273 | source = "registry+https://github.com/rust-lang/crates.io-index" 2274 | checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" 2275 | dependencies = [ 2276 | "quote", 2277 | "wasm-bindgen-macro-support", 2278 | ] 2279 | 2280 | [[package]] 2281 | name = "wasm-bindgen-macro-support" 2282 | version = "0.2.83" 2283 | source = "registry+https://github.com/rust-lang/crates.io-index" 2284 | checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" 2285 | dependencies = [ 2286 | "proc-macro2", 2287 | "quote", 2288 | "syn", 2289 | "wasm-bindgen-backend", 2290 | "wasm-bindgen-shared", 2291 | ] 2292 | 2293 | [[package]] 2294 | name = "wasm-bindgen-shared" 2295 | version = "0.2.83" 2296 | source = "registry+https://github.com/rust-lang/crates.io-index" 2297 | checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" 2298 | 2299 | [[package]] 2300 | name = "web-sys" 2301 | version = "0.3.60" 2302 | source = "registry+https://github.com/rust-lang/crates.io-index" 2303 | checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" 2304 | dependencies = [ 2305 | "js-sys", 2306 | "wasm-bindgen", 2307 | ] 2308 | 2309 | [[package]] 2310 | name = "which" 2311 | version = "4.3.0" 2312 | source = "registry+https://github.com/rust-lang/crates.io-index" 2313 | checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" 2314 | dependencies = [ 2315 | "either", 2316 | "libc", 2317 | "once_cell", 2318 | ] 2319 | 2320 | [[package]] 2321 | name = "winapi" 2322 | version = "0.3.9" 2323 | source = "registry+https://github.com/rust-lang/crates.io-index" 2324 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2325 | dependencies = [ 2326 | "winapi-i686-pc-windows-gnu", 2327 | "winapi-x86_64-pc-windows-gnu", 2328 | ] 2329 | 2330 | [[package]] 2331 | name = "winapi-i686-pc-windows-gnu" 2332 | version = "0.4.0" 2333 | source = "registry+https://github.com/rust-lang/crates.io-index" 2334 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2335 | 2336 | [[package]] 2337 | name = "winapi-util" 2338 | version = "0.1.5" 2339 | source = "registry+https://github.com/rust-lang/crates.io-index" 2340 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 2341 | dependencies = [ 2342 | "winapi", 2343 | ] 2344 | 2345 | [[package]] 2346 | name = "winapi-x86_64-pc-windows-gnu" 2347 | version = "0.4.0" 2348 | source = "registry+https://github.com/rust-lang/crates.io-index" 2349 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2350 | 2351 | [[package]] 2352 | name = "windows-sys" 2353 | version = "0.42.0" 2354 | source = "registry+https://github.com/rust-lang/crates.io-index" 2355 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 2356 | dependencies = [ 2357 | "windows_aarch64_gnullvm", 2358 | "windows_aarch64_msvc", 2359 | "windows_i686_gnu", 2360 | "windows_i686_msvc", 2361 | "windows_x86_64_gnu", 2362 | "windows_x86_64_gnullvm", 2363 | "windows_x86_64_msvc", 2364 | ] 2365 | 2366 | [[package]] 2367 | name = "windows_aarch64_gnullvm" 2368 | version = "0.42.0" 2369 | source = "registry+https://github.com/rust-lang/crates.io-index" 2370 | checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" 2371 | 2372 | [[package]] 2373 | name = "windows_aarch64_msvc" 2374 | version = "0.42.0" 2375 | source = "registry+https://github.com/rust-lang/crates.io-index" 2376 | checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" 2377 | 2378 | [[package]] 2379 | name = "windows_i686_gnu" 2380 | version = "0.42.0" 2381 | source = "registry+https://github.com/rust-lang/crates.io-index" 2382 | checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" 2383 | 2384 | [[package]] 2385 | name = "windows_i686_msvc" 2386 | version = "0.42.0" 2387 | source = "registry+https://github.com/rust-lang/crates.io-index" 2388 | checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" 2389 | 2390 | [[package]] 2391 | name = "windows_x86_64_gnu" 2392 | version = "0.42.0" 2393 | source = "registry+https://github.com/rust-lang/crates.io-index" 2394 | checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" 2395 | 2396 | [[package]] 2397 | name = "windows_x86_64_gnullvm" 2398 | version = "0.42.0" 2399 | source = "registry+https://github.com/rust-lang/crates.io-index" 2400 | checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" 2401 | 2402 | [[package]] 2403 | name = "windows_x86_64_msvc" 2404 | version = "0.42.0" 2405 | source = "registry+https://github.com/rust-lang/crates.io-index" 2406 | checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" 2407 | 2408 | [[package]] 2409 | name = "zeroize" 2410 | version = "1.5.7" 2411 | source = "registry+https://github.com/rust-lang/crates.io-index" 2412 | checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" 2413 | --------------------------------------------------------------------------------