├── .gitignore ├── renovate.json ├── .github └── workflows │ └── build.yml ├── src ├── digest.rs ├── error.rs ├── config.rs ├── state.rs ├── fetch.rs ├── activitypub.rs ├── stream.rs ├── send.rs ├── actor_cache.rs ├── db.rs ├── endpoint.rs ├── actor.rs ├── relay.rs └── main.rs ├── static ├── urlgen.js ├── style.css └── index.html ├── config.yaml ├── Cargo.toml ├── README.md ├── flake.lock ├── flake.nix ├── nixos-module.nix ├── INSTALL-UBUNTU.md ├── LICENSE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /result 3 | /private-key.pem 4 | /public-key.pem 5 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Rust build 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Dependencies 19 | run: sudo apt-get install -y libsystemd-dev 20 | - uses: actions/checkout@v4 21 | - name: Build 22 | run: cargo build --verbose 23 | - name: Run tests 24 | run: cargo test --verbose 25 | -------------------------------------------------------------------------------- /src/digest.rs: -------------------------------------------------------------------------------- 1 | use http_digest_headers::{DigestHeader, DigestMethod}; 2 | 3 | pub fn generate_header(body: &[u8]) -> Result { 4 | let mut digest_header = DigestHeader::new() 5 | .with_method(DigestMethod::SHA256, body) 6 | .map(|h| format!("{h}")) 7 | .map_err(|_| ())?; 8 | 9 | // mastodon expects uppercase algo name 10 | if digest_header.starts_with("sha-") { 11 | digest_header.replace_range(..4, "SHA-"); 12 | } 13 | // mastodon uses base64::alphabet::STANDARD, not base64::alphabet::URL_SAFE 14 | digest_header.replace_range( 15 | 7.., 16 | &digest_header[7..].replace('-', "+").replace('_', "/") 17 | ); 18 | 19 | Ok(digest_header) 20 | } 21 | -------------------------------------------------------------------------------- /static/urlgen.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | function setup(id) { 3 | var inputEl = document.getElementById(id); 4 | var preEl = document.getElementById(id + "-url"); 5 | function onChange(ev) { 6 | setTimeout(function() { 7 | var value = encodeURIComponent(inputEl.value.replace(/^#/, "")); 8 | preEl.innerText = value ? 9 | "https://" + document.location.host + "/" + id + "/" + value : 10 | "\n"; 11 | }, 10); 12 | } 13 | inputEl.addEventListener('change', onChange); 14 | inputEl.addEventListener('keyup', onChange); 15 | } 16 | 17 | setup("tag"); 18 | setup("instance"); 19 | })() 20 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | # Sources 2 | streams: 3 | # The fedi.buzz firehose stream 4 | - "https://fedi.buzz/api/v1/streaming/public" 5 | # You may list the streaming API of other instances here 6 | - "https://example.org/api/v1/streaming/public" 7 | # Starting with Mastodon v4.2.0 this API requires 8 | # an access_token that must be obtained from 9 | # https://example.com/settings/applications/new 10 | # with permission `read:statuses` 11 | - "https://example.com/api/v1/streaming/public?access_token=EfDOWQkbWFfWsZB-4Xv0axfraMTRzSV0GhB1FVAleBs" 12 | # external https hostname 13 | hostname: relay.fedi.buzz 14 | # where your reverse proxy will connect to 15 | listen_port: 3000 16 | # ActivityPub signing keypair 17 | priv_key_file: private-key.pem 18 | pub_key_file: public-key.pem 19 | # PostgreSQL 20 | db: "host=localhost user=relay password=xyz dbname=buzzrelay" 21 | # Optional Redis 22 | redis: 23 | connection: "redis://127.0.0.1:6378/" 24 | password_file: "redis_password.txt" 25 | in_topic: "relay-in" 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "buzzrelay" 3 | version = "0.1.0" 4 | edition = "2021" 5 | repository = "https://github.com/astro/buzzrelay" 6 | homepage = "https://relay.fedi.buzz" 7 | 8 | [dependencies] 9 | axum = "0.8" 10 | tower-http = { version = "0.6", features = ["fs"] } 11 | tokio = { version = "1", features = ["full", "time"] } 12 | tracing = "*" 13 | tracing-subscriber = { version = "0.3", features = ["env-filter"] } 14 | serde = { version = "1", features = ["derive"] } 15 | serde_json = "1" 16 | serde_yaml = "0.9" 17 | reqwest = { version = "0.12", features = ["json", "stream", "hickory-dns", "rustls-tls"] } 18 | sigh = "1.0" 19 | http_digest_headers = { version = "0.1.0", default-features = false, features = ["use_openssl"] } 20 | thiserror = "2" 21 | http = "1" 22 | chrono = "0.4" 23 | eventsource-stream = "0.2" 24 | futures = "0.3" 25 | tokio-postgres = "0.7" 26 | systemd = "0.10" 27 | metrics = "0.24" 28 | metrics-util = "0.20" 29 | metrics-exporter-prometheus = "0.17" 30 | deunicode = "1.4" 31 | urlencoding = "2" 32 | httpdate = "1" 33 | redis = { version = "0.32", features = ["tokio-comp", "connection-manager"] } 34 | lru = "0.16" 35 | 36 | [profile.release] 37 | lto = true 38 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | #[derive(Clone, Debug, thiserror::Error)] 4 | pub enum Error { 5 | #[error("HTTP Digest generation error")] 6 | Digest, 7 | #[error("JSON encoding error")] 8 | Json(#[from] Arc), 9 | #[error("Signature error")] 10 | Signature(#[from] Arc), 11 | #[error("Signature verification failure")] 12 | SignatureFail(String), 13 | #[error("HTTP request error")] 14 | HttpReq(#[from] Arc), 15 | #[error("HTTP client error")] 16 | Http(#[from] Arc), 17 | #[error("Invalid URI")] 18 | InvalidUri, 19 | #[error("Error response from remote")] 20 | Response(String), 21 | } 22 | 23 | impl From for Error { 24 | fn from(e: serde_json::Error) -> Self { 25 | Error::Json(Arc::new(e)) 26 | } 27 | } 28 | 29 | impl From for Error { 30 | fn from(e: reqwest::Error) -> Self { 31 | Error::Http(Arc::new(e)) 32 | } 33 | } 34 | 35 | impl From for Error { 36 | fn from(e: sigh::Error) -> Self { 37 | Error::Signature(Arc::new(e)) 38 | } 39 | } 40 | 41 | impl From for Error { 42 | fn from(e: http::Error) -> Self { 43 | Error::HttpReq(Arc::new(e)) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use sigh::{PrivateKey, PublicKey, Key}; 3 | 4 | #[derive(Clone, Deserialize)] 5 | pub struct RedisConfig { 6 | pub connection: String, 7 | pub password_file: String, 8 | pub in_topic: String, 9 | } 10 | 11 | #[derive(Clone, Deserialize)] 12 | pub struct Config { 13 | pub streams: Vec, 14 | pub db: String, 15 | pub hostname: String, 16 | pub listen_port: u16, 17 | pub redis: Option, 18 | priv_key_file: String, 19 | pub_key_file: String, 20 | } 21 | 22 | impl Config { 23 | pub fn load(config_file: &str) -> Config { 24 | let data = std::fs::read_to_string(config_file) 25 | .expect("read config"); 26 | serde_yaml::from_str(&data) 27 | .expect("parse config") 28 | } 29 | 30 | pub fn priv_key(&self) -> PrivateKey { 31 | let data = std::fs::read_to_string(&self.priv_key_file) 32 | .expect("read priv_key_file"); 33 | PrivateKey::from_pem(data.as_bytes()) 34 | .expect("priv_key") 35 | } 36 | 37 | pub fn pub_key(&self) -> PublicKey { 38 | let data = std::fs::read_to_string(&self.pub_key_file) 39 | .expect("read pub_key_file"); 40 | PublicKey::from_pem(data.as_bytes()) 41 | .expect("pub_key") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/state.rs: -------------------------------------------------------------------------------- 1 | use axum::{ 2 | extract::FromRef, 3 | }; 4 | use sigh::{PrivateKey, PublicKey}; 5 | use std::sync::Arc; 6 | use crate::{config::Config, db::Database, actor_cache::ActorCache}; 7 | 8 | #[derive(Clone)] 9 | pub struct State { 10 | pub database: Database, 11 | pub redis: Option<(redis::aio::ConnectionManager, Arc)>, 12 | pub client: Arc, 13 | pub actor_cache: ActorCache, 14 | pub hostname: Arc, 15 | pub priv_key: Arc, 16 | pub pub_key: Arc, 17 | } 18 | 19 | 20 | impl FromRef for Arc { 21 | fn from_ref(state: &State) -> Arc { 22 | state.client.clone() 23 | } 24 | } 25 | 26 | impl State { 27 | pub fn new(config: Config, database: Database, redis: Option<(redis::aio::ConnectionManager, String)>, client: reqwest::Client) -> Self { 28 | let priv_key = Arc::new(config.priv_key()); 29 | let pub_key = Arc::new(config.pub_key()); 30 | State { 31 | database, 32 | redis: redis.map(|(connection, in_topic)| (connection, Arc::new(in_topic))), 33 | client: Arc::new(client), 34 | actor_cache: ActorCache::default(), 35 | hostname: Arc::new(config.hostname), 36 | priv_key, 37 | pub_key, 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # buzzrelay 2 | 3 | A follow-only ActivityPub relay that connects to Mastodon's [Streaming 4 | API](https://docs.joinmastodon.org/methods/streaming/#public). 5 | 6 | You don't need to run this yourself, just use the instance at 7 | [relay.fedi.buzz](https://relay.fedi.buzz/). 8 | 9 | ## Setup 10 | 11 | See also: [Notes on an setting up a fediverse relay with FediBuzz on an Ubuntu server.](https://box464.com/posts/fediverse-relays/) 12 | 13 | ### Build 14 | 15 | NixOS/Flakes users are in luck: not only does this build, it also 16 | comes with a NixOS module! 17 | 18 | Anyone else installs a Rust toolchain to build with: 19 | 20 | ```bash 21 | cargo build --release 22 | ``` 23 | 24 | ### Generate signing keypair 25 | 26 | ActivityPub messages are signed using RSA keys. Generate a keypair 27 | first: 28 | 29 | ```bash 30 | openssl genrsa -out private-key.pem 4096 31 | openssl rsa -in private-key.pem -pubout -out public-key.pem 32 | ``` 33 | 34 | Let your `config.yaml` point there. 35 | 36 | ### Database 37 | 38 | Create a PostgreSQL database and user, set them in your `config.yaml`. 39 | 40 | The program will create its schema on start. 41 | 42 | ## Ethics 43 | 44 | *Should everyone connect to the streaming API of the big popular 45 | Mastodon instances?* 46 | 47 | Once these connections become a problem, they may become disallowed, 48 | resulting in problems for everyone. That's why **fedi.buzz** serves 49 | the firehose feed through the streaming API, too. 50 | 51 | You can let this service use **fedi.buzz** as listed in the default 52 | `config.yaml`. 53 | -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #EFDFFF; 3 | color: #0F1F2F; 4 | font-family: sans-serif; 5 | margin: 0 auto; 6 | max-width: 70rem; 7 | } 8 | a { 9 | text-decoration: none; 10 | font-weight: bold; 11 | color: #9F2F1F; 12 | } 13 | h1 { 14 | text-align: center; 15 | font-size: 600%; 16 | text-shadow: 0 0.2rem 0.2rem #9FAFBF; 17 | letter-spacing: 0.1rem; 18 | margin: 1rem 0 0; 19 | } 20 | header p { 21 | margin: 0 auto 1rem; 22 | text-align: center; 23 | font-style: italic; 24 | } 25 | h2 { 26 | margin: 3rem auto 1rem; 27 | max-width: 38rem; 28 | } 29 | p, ol, pre { 30 | margin: 2rem auto; 31 | max-width: 30rem; 32 | padding: 0; 33 | line-height: 2em; 34 | } 35 | ol > li { 36 | position: relative; 37 | margin: 0.5rem; 38 | padding: 0; 39 | } 40 | ol > li > code { 41 | padding-left: 2rem; 42 | color: #888; 43 | position: absolute; 44 | right: 0; 45 | } 46 | ol > li > code::before { 47 | content: "https://…"; 48 | } 49 | 50 | .boxes { 51 | display: flex; 52 | flex-direction: row; 53 | justify-content: space-around; 54 | flex-wrap: wrap; 55 | } 56 | .boxes article { 57 | background-color: #EFDFFF; 58 | padding: 0.5rem 1rem; 59 | text-align: center; 60 | margin: 1rem 0.5rem; 61 | min-width: 25rem; 62 | } 63 | .boxes article pre { 64 | margin: 1rem auto; 65 | padding: 0.5rem; 66 | background-color: #DFCFEF; 67 | } 68 | 69 | .faq { 70 | margin: 4rem; 71 | } 72 | 73 | footer { 74 | margin-top: 8rem; 75 | text-align: center; 76 | } 77 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1763191728, 6 | "narHash": "sha256-esRhOS0APE6k40Hs/jjReXg+rx+J5LkWw7cuWFKlwYA=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "1d4c88323ac36805d09657d13a5273aea1b34f0c", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "NixOS", 14 | "ref": "nixpkgs-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs", 22 | "utils": "utils" 23 | } 24 | }, 25 | "systems": { 26 | "locked": { 27 | "lastModified": 1681028828, 28 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 29 | "owner": "nix-systems", 30 | "repo": "default", 31 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 32 | "type": "github" 33 | }, 34 | "original": { 35 | "owner": "nix-systems", 36 | "repo": "default", 37 | "type": "github" 38 | } 39 | }, 40 | "utils": { 41 | "inputs": { 42 | "systems": "systems" 43 | }, 44 | "locked": { 45 | "lastModified": 1731533236, 46 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 47 | "owner": "numtide", 48 | "repo": "flake-utils", 49 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "numtide", 54 | "repo": "flake-utils", 55 | "type": "github" 56 | } 57 | } 58 | }, 59 | "root": "root", 60 | "version": 7 61 | } 62 | -------------------------------------------------------------------------------- /src/fetch.rs: -------------------------------------------------------------------------------- 1 | use std::time::SystemTime; 2 | use http::StatusCode; 3 | use serde::de::DeserializeOwned; 4 | use sigh::{PrivateKey, SigningConfig, alg::RsaSha256}; 5 | use tokio::task::spawn_blocking; 6 | use crate::{digest, error::Error}; 7 | 8 | pub async fn authorized_fetch( 9 | client: &reqwest::Client, 10 | uri: &str, 11 | key_id: &str, 12 | private_key: &PrivateKey, 13 | ) -> Result 14 | where 15 | T: DeserializeOwned, 16 | { 17 | let url = reqwest::Url::parse(uri) 18 | .map_err(|_| Error::InvalidUri)?; 19 | let host = format!("{}", url.host().ok_or(Error::InvalidUri)?); 20 | let digest_header = digest::generate_header(&[]) 21 | .expect("digest::generate_header"); 22 | let mut req = http::Request::builder() 23 | .uri(uri) 24 | .header("host", &host) 25 | .header("content-type", "application/activity+json") 26 | .header("date", httpdate::fmt_http_date(SystemTime::now())) 27 | .header("accept", "application/activity+json") 28 | .header("digest", digest_header) 29 | .body(vec![])?; 30 | let private_key = private_key.clone(); 31 | let key_id = key_id.to_string(); 32 | let req = spawn_blocking(move || { 33 | SigningConfig::new(RsaSha256, &private_key, &key_id).sign(&mut req)?; 34 | Ok(req) 35 | }) 36 | .await 37 | .map_err(|e| Error::Response(format!("{e}")))? 38 | .map_err(|e: sigh::Error| Error::Response(format!("{e}")))?; 39 | let req: reqwest::Request = req.try_into()?; 40 | let res = client.execute(req).await?; 41 | if res.status() >= StatusCode::OK && res.status() < StatusCode::MULTIPLE_CHOICES { 42 | Ok(res.json().await?) 43 | } else { 44 | Err(Error::Response(res.text().await?)) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/activitypub.rs: -------------------------------------------------------------------------------- 1 | use axum::{response::IntoResponse, Json}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Debug, Clone, Serialize, Deserialize)] 5 | pub struct Actor { 6 | #[serde(rename = "@context")] 7 | pub jsonld_context: serde_json::Value, 8 | #[serde(rename = "type")] 9 | pub actor_type: String, 10 | pub id: String, 11 | pub name: Option, 12 | pub icon: Option, 13 | pub inbox: String, 14 | pub outbox: Option, 15 | pub endpoints: Option, 16 | #[serde(rename = "publicKey")] 17 | pub public_key: ActorPublicKey, 18 | #[serde(rename = "preferredUsername")] 19 | pub preferred_username: Option, 20 | } 21 | 22 | #[derive(Debug, Clone, Serialize, Deserialize)] 23 | pub struct ActorEndpoints { 24 | #[serde(rename = "sharedInbox")] 25 | pub shared_inbox: String, 26 | } 27 | 28 | #[derive(Debug, Clone, Serialize, Deserialize)] 29 | pub struct ActorPublicKey { 30 | pub id: String, 31 | pub owner: Option, 32 | #[serde(rename = "publicKeyPem")] 33 | pub pem: String, 34 | } 35 | 36 | /// `ActivityPub` "activity" 37 | #[derive(Debug, Clone, Serialize, Deserialize)] 38 | pub struct Action { 39 | #[serde(rename = "@context")] 40 | pub jsonld_context: serde_json::Value, 41 | #[serde(rename = "type")] 42 | pub action_type: String, 43 | pub id: String, 44 | pub actor: String, 45 | pub to: Option, 46 | pub object: Option, 47 | } 48 | 49 | impl IntoResponse for Actor { 50 | fn into_response(self) -> axum::response::Response { 51 | ([("content-type", "application/activity+json")], 52 | Json(self)).into_response() 53 | } 54 | } 55 | 56 | #[derive(Debug, Clone, Serialize, Deserialize)] 57 | pub struct Media { 58 | #[serde(rename = "type")] 59 | pub media_type: Option, 60 | #[serde(rename = "mediaType")] 61 | pub content_type: Option, 62 | pub url: String, 63 | } 64 | -------------------------------------------------------------------------------- /src/stream.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | use futures::{Stream, StreamExt}; 3 | use eventsource_stream::Eventsource; 4 | use tokio::{ 5 | sync::mpsc::{channel, Receiver}, 6 | time::sleep, 7 | }; 8 | 9 | #[derive(Debug)] 10 | pub enum StreamError { 11 | Http(reqwest::Error), 12 | HttpStatus(reqwest::StatusCode), 13 | InvalidContentType, 14 | } 15 | 16 | async fn run(url: &str) -> Result, StreamError> { 17 | let client = reqwest::Client::new(); 18 | let res = client.get(url) 19 | .timeout(Duration::MAX) 20 | .send() 21 | .await 22 | .map_err(StreamError::Http)?; 23 | if res.status() != 200 { 24 | return Err(StreamError::HttpStatus(res.status())); 25 | } 26 | let ct = res.headers().get("content-type") 27 | .and_then(|c| c.to_str().ok()); 28 | if ct != Some("text/event-stream") { 29 | return Err(StreamError::InvalidContentType); 30 | } 31 | 32 | let src = res.bytes_stream() 33 | .eventsource() 34 | .filter_map(|result| async { 35 | let result = result.ok()?; 36 | if result.event == "update" { 37 | Some(result) 38 | } else { 39 | None 40 | } 41 | }) 42 | .map(|event| event.data); 43 | Ok(src) 44 | } 45 | 46 | pub fn spawn(hosts: impl Iterator>) -> Receiver { 47 | let (tx, rx) = channel(1024); 48 | for host in hosts { 49 | let host = host.into(); 50 | let tx = tx.clone(); 51 | tokio::spawn(async move { 52 | loop { 53 | match run(&host).await { 54 | Ok(stream) => 55 | stream.for_each(|post| async { 56 | tx.send(post).await.unwrap(); 57 | }).await, 58 | Err(StreamError::Http(e)) => 59 | tracing::error!("stream http error: {:?}", e), 60 | Err(StreamError::HttpStatus(status)) => 61 | tracing::error!("stream http status: {:?}", status), 62 | Err(StreamError::InvalidContentType) => 63 | tracing::error!("stream invalid content-type"), 64 | } 65 | 66 | sleep(Duration::from_secs(1)).await; 67 | } 68 | }); 69 | } 70 | rx 71 | } 72 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 4 | utils.url = "github:numtide/flake-utils"; 5 | }; 6 | 7 | outputs = { self, nixpkgs, utils }: 8 | let 9 | makeBuzzrelay = pkgs: 10 | pkgs.rustPlatform.buildRustPackage rec { 11 | pname = "buzzrelay"; 12 | version = ( 13 | pkgs.lib.importTOML ./Cargo.toml 14 | ).package.version + "-${toString (self.sourceInfo.revCount or 0)}-${self.sourceInfo.shortRev or "dirty"}"; 15 | src = pkgs.runCommand "${pname}-${version}-src" { 16 | preferLocalBuild = true; 17 | } '' 18 | mkdir $out 19 | cd ${self} 20 | cp -ar Cargo.{toml,lock} static src $out/ 21 | ''; 22 | cargoLock.lockFile = ./Cargo.lock; 23 | nativeBuildInputs = with pkgs; [ pkg-config rustPackages.clippy ]; 24 | buildInputs = with pkgs; [ openssl systemd ]; 25 | postInstall = '' 26 | mkdir -p $out/share/buzzrelay 27 | cp -r static $out/share/buzzrelay/ 28 | ''; 29 | postCheck = '' 30 | cargo clippy --all --all-features --tests -- \ 31 | -D warnings 32 | ''; 33 | meta = { 34 | description = "The buzzing ActivityPub relay"; 35 | mainProgram = "buzzrelay"; 36 | }; 37 | }; 38 | in 39 | utils.lib.eachDefaultSystem 40 | (system: 41 | let 42 | pkgs = nixpkgs.legacyPackages.${system}; 43 | in 44 | { 45 | packages = { 46 | default = self.packages."${system}".buzzrelay; 47 | buzzrelay = makeBuzzrelay pkgs; 48 | }; 49 | 50 | apps.default = utils.lib.mkApp { 51 | drv = self.packages."${system}".default; 52 | }; 53 | 54 | devShells.default = with pkgs; mkShell { 55 | nativeBuildInputs = [ 56 | pkg-config 57 | openssl systemd 58 | cargo rustc rustfmt rustPackages.clippy rust-analyzer cargo-outdated 59 | ]; 60 | RUST_SRC_PATH = rustPlatform.rustLibSrc; 61 | }; 62 | }) 63 | // { 64 | overlays.default = (_: prev: { 65 | buzzrelay = makeBuzzrelay prev; 66 | }); 67 | 68 | nixosModules.default = import ./nixos-module.nix { inherit self; }; 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /src/send.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | sync::Arc, 3 | time::{Instant, SystemTime}, 4 | }; 5 | use http::StatusCode; 6 | use metrics::histogram; 7 | use serde::Serialize; 8 | use sigh::{PrivateKey, SigningConfig, alg::RsaSha256}; 9 | use tokio::task::spawn_blocking; 10 | use crate::{digest, error::Error}; 11 | 12 | pub async fn send( 13 | client: &reqwest::Client, 14 | uri: &str, 15 | key_id: &str, 16 | private_key: &PrivateKey, 17 | body: &T, 18 | ) -> Result<(), Error> { 19 | let body = Arc::new( 20 | serde_json::to_vec(body)? 21 | ); 22 | send_raw(client, uri, key_id, private_key, body).await 23 | } 24 | 25 | pub async fn send_raw( 26 | client: &reqwest::Client, 27 | uri: &str, 28 | key_id: &str, 29 | private_key: &PrivateKey, 30 | body: Arc>, 31 | ) -> Result<(), Error> { 32 | let url = reqwest::Url::parse(uri) 33 | .map_err(|_| Error::InvalidUri)?; 34 | let host = format!("{}", url.host().ok_or(Error::InvalidUri)?); 35 | let digest_header = digest::generate_header(&body) 36 | .map_err(|()| Error::Digest)?; 37 | let mut req = http::Request::builder() 38 | .method("POST") 39 | .uri(uri) 40 | .header("host", &host) 41 | .header("content-type", "application/activity+json") 42 | .header("date", httpdate::fmt_http_date(SystemTime::now())) 43 | .header("digest", digest_header) 44 | .body(body.as_ref().clone())?; 45 | let t1 = Instant::now(); 46 | let private_key = private_key.clone(); 47 | let key_id = key_id.to_string(); 48 | let req = spawn_blocking(move || { 49 | SigningConfig::new(RsaSha256, &private_key, &key_id).sign(&mut req)?; 50 | Ok(req) 51 | }) 52 | .await 53 | .map_err(|e| Error::Response(format!("{e}")))? 54 | .map_err(|e: sigh::Error| Error::Response(format!("{e}")))?; 55 | let t2 = Instant::now(); 56 | let req: reqwest::Request = req.try_into()?; 57 | let res = client.execute(req).await?; 58 | let t3 = Instant::now(); 59 | histogram!("relay_http_request_duration") 60 | .record(t2 - t1); 61 | if res.status() >= StatusCode::OK && res.status() < StatusCode::MULTIPLE_CHOICES { 62 | histogram!("relay_http_response_duration", "res" => "ok") 63 | .record(t3 - t2); 64 | Ok(()) 65 | } else { 66 | histogram!("relay_http_response_duration", "res" => "err") 67 | .record(t3 - t2); 68 | let response = res.text().await?; 69 | Err(Error::Response(response)) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/actor_cache.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | sync::Arc, 4 | time::Instant, 5 | }; 6 | 7 | use futures::Future; 8 | use lru::LruCache; 9 | use tokio::sync::{Mutex, oneshot}; 10 | 11 | use crate::activitypub::Actor; 12 | use crate::error::Error; 13 | 14 | 15 | #[allow(clippy::type_complexity)] 16 | #[derive(Clone)] 17 | pub struct ActorCache { 18 | cache: Arc, Error>>>>, 19 | queues: Arc, Error>>>>>>, 20 | } 21 | 22 | impl Default for ActorCache { 23 | fn default() -> Self { 24 | ActorCache { 25 | cache: Arc::new(Mutex::new( 26 | LruCache::new(std::num::NonZeroUsize::new(64).unwrap()) 27 | )), 28 | queues: Arc::new(Mutex::new(HashMap::new())), 29 | } 30 | } 31 | } 32 | 33 | impl ActorCache { 34 | pub async fn get(&self, k: &str, f: F) -> Result, Error> 35 | where 36 | F: (FnOnce() -> R) + Send + 'static, 37 | R: Future> + Send, 38 | { 39 | let begin = Instant::now(); 40 | 41 | let mut lru = self.cache.lock().await; 42 | if let Some(v) = lru.get(k) { 43 | return v.clone(); 44 | } 45 | drop(lru); 46 | 47 | let (tx, rx) = oneshot::channel(); 48 | let mut new = false; 49 | let mut queues = self.queues.lock().await; 50 | let queue = queues.entry(k.to_string()) 51 | .or_insert_with(|| { 52 | new = true; 53 | Vec::with_capacity(1) 54 | }); 55 | queue.push(tx); 56 | drop(queues); 57 | 58 | if new { 59 | let k = k.to_string(); 60 | let cache = self.cache.clone(); 61 | let queues = self.queues.clone(); 62 | tokio::spawn(async move { 63 | let result = f().await 64 | .map(Arc::new); 65 | 66 | let mut lru = cache.lock().await; 67 | lru.put(k.clone(), result.clone()); 68 | drop(lru); 69 | 70 | let mut queues = queues.lock().await; 71 | let queue = queues.remove(&k) 72 | .expect("queues.remove"); 73 | let queue_len = queue.len(); 74 | let mut notified = 0usize; 75 | for tx in queue { 76 | if let Ok(()) = tx.send(result.clone()) { 77 | notified += 1; 78 | } 79 | } 80 | 81 | let end = Instant::now(); 82 | tracing::info!("Notified {notified}/{queue_len} endpoint verifications for actor {k} in {:?}", end - begin); 83 | }); 84 | } 85 | 86 | rx.await.unwrap() 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /nixos-module.nix: -------------------------------------------------------------------------------- 1 | { self }: 2 | { config, lib, pkgs, ... }: { 3 | options.services.buzzrelay = with lib; { 4 | enable = mkEnableOption "Enable Fedi.buzz relay"; 5 | package = mkOption { 6 | type = types.package; 7 | default = self.packages.${pkgs.stdenv.system}.buzzrelay; 8 | }; 9 | streams = mkOption { 10 | type = with types; listOf str; 11 | default = [ 12 | "https://fedi.buzz/api/v1/streaming/public" 13 | ]; 14 | }; 15 | listenPort = mkOption { 16 | type = types.int; 17 | default = 8000; 18 | }; 19 | hostName = mkOption { 20 | type = types.str; 21 | }; 22 | privKeyFile = mkOption { 23 | type = types.str; 24 | }; 25 | pubKeyFile = mkOption { 26 | type = types.str; 27 | }; 28 | database = mkOption { 29 | type = types.str; 30 | default = "buzzrelay"; 31 | }; 32 | user = mkOption { 33 | type = types.str; 34 | default = "buzzrelay"; 35 | }; 36 | group = mkOption { 37 | type = types.str; 38 | default = "buzzrelay"; 39 | }; 40 | logLevel = mkOption { 41 | type = types.enum [ "ERROR" "WARN" "INFO" "DEBUG" "TRACE" ]; 42 | default = "INFO"; 43 | }; 44 | 45 | redis = { 46 | connection = mkOption { 47 | type = with types; nullOr str; 48 | default = null; 49 | }; 50 | passwordFile = mkOption { 51 | type = with types; nullOr path; 52 | default = null; 53 | }; 54 | inTopic = mkOption { 55 | type = types.str; 56 | default = "relay-in"; 57 | }; 58 | }; 59 | }; 60 | 61 | config = 62 | let 63 | cfg = config.services.buzzrelay; 64 | configFile = builtins.toFile "buzzrelay.yaml" ( 65 | lib.generators.toYAML {} { 66 | streams = cfg.streams; 67 | hostname = cfg.hostName; 68 | listen_port = cfg.listenPort; 69 | priv_key_file = cfg.privKeyFile; 70 | pub_key_file = cfg.pubKeyFile; 71 | db = "host=/var/run/postgresql user=${cfg.user} dbname=${cfg.database}"; 72 | redis = if cfg.redis.connection != null 73 | then { 74 | connection = cfg.redis.connection; 75 | password_file = cfg.redis.passwordFile; 76 | in_topic = cfg.redis.inTopic; 77 | } 78 | else null; 79 | }); 80 | buzzrelay = cfg.package; 81 | in 82 | lib.mkIf cfg.enable { 83 | users.users.${cfg.user} = { 84 | inherit (cfg) group; 85 | isSystemUser = true; 86 | }; 87 | users.groups.${cfg.group} = {}; 88 | 89 | services.postgresql = { 90 | enable = true; 91 | ensureDatabases = [ cfg.database ]; 92 | ensureUsers = [ { 93 | name = cfg.user; 94 | ensureDBOwnership = true; 95 | } ]; 96 | }; 97 | 98 | systemd.services.buzzrelay = { 99 | wantedBy = [ "multi-user.target" ]; 100 | # TODO: change postgresql.service to postgresql.target on 25.11 101 | after = [ "postgresql.service" "network-online.target" ]; 102 | wants = [ "network-online.target" ]; 103 | environment.RUST_LOG = "buzzrelay=${cfg.logLevel}"; 104 | serviceConfig = { 105 | Type = "notify"; 106 | WorkingDirectory = "${buzzrelay}/share/buzzrelay"; 107 | ExecStart = "${lib.getExe buzzrelay} ${lib.escapeShellArg configFile}"; 108 | User = cfg.user; 109 | Group = cfg.group; 110 | ProtectSystem = "full"; 111 | Restart = "always"; 112 | RestartSec = "1s"; 113 | WatchdogSec = "1800s"; 114 | LimitNOFile = 40000; 115 | }; 116 | }; 117 | }; 118 | } 119 | -------------------------------------------------------------------------------- /src/db.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::Arc, time::Instant}; 2 | use metrics::histogram; 3 | use tokio_postgres::{Client, Error, NoTls, Statement}; 4 | 5 | 6 | const CREATE_SCHEMA_COMMANDS: &[&str] = &[ 7 | "CREATE TABLE IF NOT EXISTS follows (id TEXT NOT NULL, inbox TEXT NOT NULL, actor TEXT NOT NULL, UNIQUE (inbox, actor))", 8 | "CREATE INDEX IF NOT EXISTS follows_actor ON follows (actor) INCLUDE (inbox)", 9 | ]; 10 | 11 | #[derive(Clone)] 12 | pub struct Database { 13 | inner: Arc, 14 | } 15 | 16 | struct DatabaseInner { 17 | client: Client, 18 | add_follow: Statement, 19 | del_follow: Statement, 20 | get_following_inboxes: Statement, 21 | get_follows_count: Statement, 22 | get_followers_count: Statement, 23 | } 24 | 25 | impl Database { 26 | pub async fn connect(conn_str: &str) -> Self { 27 | let (client, connection) = tokio_postgres::connect(conn_str, NoTls) 28 | .await 29 | .unwrap(); 30 | 31 | tokio::spawn(async move { 32 | if let Err(e) = connection.await { 33 | tracing::error!("postgresql: {}", e); 34 | } 35 | }); 36 | 37 | for command in CREATE_SCHEMA_COMMANDS { 38 | client.execute(*command, &[]) 39 | .await 40 | .unwrap(); 41 | } 42 | let add_follow = client.prepare("INSERT INTO follows (id, inbox, actor) VALUES ($1, $2, $3)") 43 | .await 44 | .unwrap(); 45 | let del_follow = client.prepare("DELETE FROM follows WHERE id=$1 AND actor=$2") 46 | .await 47 | .unwrap(); 48 | let get_following_inboxes = client.prepare("SELECT DISTINCT inbox FROM follows WHERE actor=$1") 49 | .await 50 | .unwrap(); 51 | let get_follows_count = client.prepare("SELECT COUNT(id) FROM follows") 52 | .await 53 | .unwrap(); 54 | let get_followers_count = client.prepare("SELECT COUNT(DISTINCT id) FROM follows") 55 | .await 56 | .unwrap(); 57 | 58 | Database { 59 | inner: Arc::new(DatabaseInner { 60 | client, 61 | add_follow, 62 | del_follow, 63 | get_following_inboxes, 64 | get_follows_count, 65 | get_followers_count, 66 | }), 67 | } 68 | } 69 | 70 | pub async fn add_follow(&self, id: &str, inbox: &str, actor: &str) -> Result<(), Error> { 71 | let t1 = Instant::now(); 72 | self.inner.client.execute(&self.inner.add_follow, &[&id, &inbox, &actor]) 73 | .await?; 74 | let t2 = Instant::now(); 75 | histogram!("postgres_query_duration", "query" => "add_follow") 76 | .record(t2 - t1); 77 | Ok(()) 78 | } 79 | 80 | pub async fn del_follow(&self, id: &str, actor: &str) -> Result<(), Error> { 81 | let t1 = Instant::now(); 82 | self.inner.client.execute(&self.inner.del_follow, &[&id, &actor]) 83 | .await?; 84 | let t2 = Instant::now(); 85 | histogram!("postgres_query_duration", "query" => "del_follow") 86 | .record(t2 - t1); 87 | Ok(()) 88 | } 89 | 90 | pub async fn get_following_inboxes(&self, actor: &str) -> Result, Error> { 91 | let t1 = Instant::now(); 92 | let rows = self.inner.client.query(&self.inner.get_following_inboxes, &[&actor]) 93 | .await?; 94 | let t2 = Instant::now(); 95 | histogram!("postgres_query_duration", "query" => "get_following_inboxes") 96 | .record(t2 - t1); 97 | Ok(rows.into_iter() 98 | .map(|row| row.get(0)) 99 | ) 100 | } 101 | 102 | pub async fn get_follows_count(&self) -> Result { 103 | let row = self.inner.client.query_one(&self.inner.get_follows_count, &[]) 104 | .await?; 105 | Ok(row.get(0)) 106 | } 107 | 108 | pub async fn get_followers_count(&self) -> Result { 109 | let row = self.inner.client.query_one(&self.inner.get_followers_count, &[]) 110 | .await?; 111 | Ok(row.get(0)) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #FediBuzz Relay 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |

#FediBuzz Relay

18 |

The customizable ActivityPub relay service

19 |
20 | 21 |
22 |

23 | Mastodon and many other ActivityPub-compatible services live 24 | and breathe decentralization. Yet it can get lonely on a small 25 | instance. Mastoadmins can bring the global buzz of toots into 26 | the Federated Timeline of their small server by 27 | following ActivityPub relays. 28 |

29 |

30 | With this service #FediBuzz 31 | provides relay endpoints for you to customize. 32 |

33 |
    34 |
  1. In Mastodon: click Preferences in the navigation sidebar
  2. 35 |
  3. Navigate to Administration
  4. 36 |
  5. Navigate to Relays /admin/relays
  6. 37 |
  7. Click the Add new relay button /admin/relays/new
  8. 38 |
  9. Generate a relay address below
  10. 39 |
  11. Copy & paste into Mastodon's preferences
  12. 40 |
  13. Hit the Save and enable button
  14. 41 |
42 |
43 | 44 |
45 | 50 |
51 |

Follow posts by #tag

52 |
53 | 54 |
55 |
 56 |         
57 |
58 |
59 |

Follow posts by instance

60 |
61 | 62 |
63 |
 64 |         
65 |
66 |
67 | 68 |
69 |

Can I follow all my city's hashtags for all dates in the year?

70 |

71 | Putting dates in hashtags is popular. We are aware, and our 72 | solution is very simple: posts with hashtags that end in 73 | digits are additionally delivered to all followers that follow 74 | the hashtag without the trailing digits. 75 |

76 |

77 | That means, if you follow #dd on this relay, 78 | you'll also get #dd1302, #dd1402, 79 | #dd1502, and many more! 80 |

81 | 82 |

Will this service get me undesirable content?

83 |

84 | To steer free of the worst, #FediBuzz ignores anyone from 85 | instances that appear in 86 | the Garden 87 | Fence blocklist, which we pull regularly. However, because 88 | we relay links to content in real-time, this service will 89 | never do any manual filtering itself. 90 |

91 | 92 |

Is it cool to follow a few thousand tags/instances to build my own firehose?

93 |

94 | Please don't, it's inefficient. Take a look at #FediBuzz' 95 | federated 96 | timeline API instead: 97 |

98 |
https://fedi.buzz/api/v1/streaming/public
99 |
100 | 101 | 108 | 109 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /src/endpoint.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use axum::{ 4 | body::{Bytes, Body}, 5 | extract::{FromRef, FromRequest}, 6 | http::{header::CONTENT_TYPE, Request, StatusCode}, 7 | }; 8 | use http_digest_headers::DigestHeader; 9 | use sigh::{Signature, PublicKey, Key, PrivateKey}; 10 | 11 | use crate::fetch::authorized_fetch; 12 | use crate::activitypub::Actor; 13 | use crate::error::Error; 14 | use crate::actor_cache::ActorCache; 15 | 16 | 17 | const SIGNATURE_HEADERS_REQUIRED: &[&str] = &[ 18 | "(request-target)", 19 | "host", "date", 20 | "digest", 21 | ]; 22 | 23 | pub struct Endpoint<'a> { 24 | pub payload: serde_json::Value, 25 | signature: Signature<'a>, 26 | pub remote_actor_uri: String, 27 | } 28 | 29 | impl FromRequest for Endpoint<'_> 30 | where 31 | S: Send + Sync, 32 | Arc: FromRef, 33 | { 34 | type Rejection = (StatusCode, String); 35 | 36 | async fn from_request(req: Request, state: &S) -> Result { 37 | // validate content-type 38 | let Some(content_type) = req.headers() 39 | .get(CONTENT_TYPE) 40 | .and_then(|value| value.to_str().ok()) 41 | .and_then(|value| value.split(';').next()) else { 42 | return Err((StatusCode::UNSUPPORTED_MEDIA_TYPE, "No content-type".to_string())); 43 | }; 44 | if ! (content_type.starts_with("application/json") || 45 | (content_type.starts_with("application/") && content_type.ends_with("+json"))) 46 | { 47 | return Err((StatusCode::UNSUPPORTED_MEDIA_TYPE, "Invalid content-type".to_string())); 48 | } 49 | // get signature before consuming req 50 | let signature = Signature::from(&req); 51 | // check signature fields 52 | let signature_headers = signature.headers() 53 | .ok_or((StatusCode::BAD_REQUEST, "No signed headers".to_string()))?; 54 | for header in SIGNATURE_HEADERS_REQUIRED { 55 | if !signature_headers.iter().any(|h| h == header) { 56 | return Err((StatusCode::BAD_REQUEST, format!("Header {header:?} not signed"))); 57 | } 58 | } 59 | 60 | // parse digest 61 | let mut digest_header: String = req.headers().get("digest") 62 | .ok_or((StatusCode::BAD_REQUEST, "Missing Digest: header".to_string()))? 63 | .to_str() 64 | .map_err(|_| (StatusCode::BAD_REQUEST, "Digest: header contained invalid characters".to_string()))? 65 | .to_string(); 66 | // fixup digest header 67 | if digest_header.starts_with("SHA-") { 68 | digest_header.replace_range(..4, "sha-"); 69 | } 70 | // mastodon uses base64::alphabet::STANDARD, not base64::alphabet::URL_SAFE 71 | digest_header = digest_header.replace('+', "-") 72 | .replace('/', "_"); 73 | let digest: DigestHeader = digest_header.parse() 74 | .map_err(|e| (StatusCode::BAD_REQUEST, format!("Cannot parse Digest: header: {e}")))?; 75 | // read body 76 | let bytes = Bytes::from_request(req, state).await 77 | .map_err(|e| (StatusCode::BAD_REQUEST, format!("Body: {e}")))?; 78 | // validate digest 79 | if ! digest.verify(&bytes).unwrap_or(false) { 80 | return Err((StatusCode::BAD_REQUEST, "Digest didn't match".to_string())); 81 | } 82 | // parse body 83 | let payload: serde_json::Value = serde_json::from_slice(&bytes) 84 | .map_err(|_| (StatusCode::BAD_REQUEST, "Error parsing JSON".to_string()))?; 85 | let remote_actor_uri = if let Some(serde_json::Value::String(actor_uri)) = payload.get("actor") { 86 | actor_uri.to_string() 87 | } else { 88 | return Err((StatusCode::BAD_REQUEST, "Actor missing".to_string())); 89 | }; 90 | 91 | Ok(Endpoint { payload, signature, remote_actor_uri }) 92 | } 93 | } 94 | 95 | impl Endpoint<'_> { 96 | /// Validates the requesting actor 97 | pub async fn remote_actor( 98 | &self, 99 | client: &reqwest::Client, 100 | cache: &ActorCache, 101 | key_id: String, 102 | private_key: Arc, 103 | ) -> Result, Error> { 104 | let client = client.clone(); 105 | let url = self.remote_actor_uri.clone(); 106 | let remote_actor = cache.get(&self.remote_actor_uri, || async move { 107 | tracing::info!("GET actor {}", url); 108 | let actor: Actor = serde_json::from_value( 109 | authorized_fetch(&client, &url, &key_id, &private_key).await? 110 | )?; 111 | Ok(actor) 112 | }).await?; 113 | 114 | let public_key = PublicKey::from_pem(remote_actor.public_key.pem.as_bytes())?; 115 | if ! (self.signature.verify(&public_key)?) { 116 | tracing::error!("Cannot verify signature for {}: {:?}", self.remote_actor_uri, self.payload); 117 | return Err(Error::SignatureFail(self.remote_actor_uri.clone())); 118 | } 119 | 120 | Ok(remote_actor) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/actor.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use deunicode::deunicode; 3 | use serde_json::json; 4 | use sigh::{PublicKey, Key}; 5 | 6 | use crate::activitypub; 7 | 8 | #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] 9 | #[allow(clippy::enum_variant_names)] 10 | pub enum ActorKind { 11 | TagRelay(String), 12 | InstanceRelay(String), 13 | LanguageRelay(String), 14 | } 15 | 16 | impl ActorKind { 17 | pub fn from_tag(tag: &str) -> Self { 18 | let tag = deunicode(tag) 19 | .to_lowercase() 20 | .replace(char::is_whitespace, "") 21 | .replace('-', ""); 22 | ActorKind::TagRelay(tag) 23 | } 24 | 25 | pub fn from_language(language: &str) -> Option { 26 | let language = language.to_lowercase() 27 | .chars() 28 | .take_while(|c| c.is_alphabetic()) 29 | .collect::(); 30 | if language.is_empty() { 31 | None 32 | } else { 33 | Some(ActorKind::LanguageRelay(language)) 34 | } 35 | } 36 | } 37 | 38 | #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] 39 | pub struct Actor { 40 | pub host: Arc, 41 | pub kind: ActorKind, 42 | } 43 | 44 | impl Actor { 45 | pub fn from_uri(mut uri: &str) -> Option { 46 | let kind; 47 | let host; 48 | if uri.starts_with("acct:tag-") { 49 | let off = "acct:tag-".len(); 50 | let at = uri.find('@')?; 51 | kind = ActorKind::from_tag(&uri[off..at]); 52 | host = Arc::new(uri[at + 1..].to_string()); 53 | } else if uri.starts_with("acct:instance-") { 54 | let off = "acct:instance-".len(); 55 | let at = uri.find('@')?; 56 | kind = ActorKind::InstanceRelay(uri[off..at].to_lowercase()); 57 | host = Arc::new(uri[at + 1..].to_string()); 58 | } else if uri.starts_with("acct:language-") { 59 | let off = "acct:language-".len(); 60 | let at = uri.find('@')?; 61 | kind = ActorKind::from_language(&uri[off..at])?; 62 | host = Arc::new(uri[at + 1..].to_string()); 63 | } else if uri.starts_with("https://") { 64 | uri = &uri[8..]; 65 | 66 | let parts = uri.split('/').collect::>(); 67 | if parts.len() != 3 { 68 | return None; 69 | } 70 | 71 | let Ok(topic) = urlencoding::decode(parts[2]) else { return None; }; 72 | kind = match parts[1] { 73 | "tag" => 74 | ActorKind::TagRelay(topic.to_string()), 75 | "instance" => 76 | ActorKind::InstanceRelay(topic.to_string()), 77 | "language" => 78 | ActorKind::LanguageRelay(topic.to_string()), 79 | _ => 80 | return None, 81 | }; 82 | host = Arc::new(parts[0].to_string()); 83 | } else { 84 | return None; 85 | } 86 | Some(Actor { host, kind }) 87 | } 88 | 89 | pub fn from_object(object: &serde_json::Value) -> Option { 90 | let mut target: Option = None; 91 | if let Some(object) = object.as_str() { 92 | target = Some(object.to_string()); 93 | } 94 | if let Some(object_0) = object.as_array() 95 | .and_then(|object| { 96 | if object.len() == 1 { 97 | object.first() 98 | } else { 99 | None 100 | } 101 | }).and_then(|object_0| object_0.as_str()) 102 | { 103 | target = Some(object_0.to_string()); 104 | } 105 | 106 | target.and_then(|target| Self::from_uri(&target)) 107 | } 108 | 109 | pub fn uri(&self) -> String { 110 | match &self.kind { 111 | ActorKind::TagRelay(tag) => 112 | format!("https://{}/tag/{}", self.host, tag), 113 | ActorKind::InstanceRelay(instance) => 114 | format!("https://{}/instance/{}", self.host, instance), 115 | ActorKind::LanguageRelay(language) => 116 | format!("https://{}/language/{}", self.host, language), 117 | } 118 | } 119 | 120 | pub fn key_id(&self) -> String { 121 | format!("{}#key", self.uri()) 122 | } 123 | 124 | pub fn as_activitypub(&self, pub_key: &PublicKey) -> activitypub::Actor { 125 | activitypub::Actor { 126 | jsonld_context: json!([ 127 | "https://www.w3.org/ns/activitystreams", 128 | "https://w3id.org/security/v1" 129 | ]), 130 | actor_type: "Service".to_string(), 131 | id: self.uri(), 132 | name: Some(match &self.kind { 133 | ActorKind::TagRelay(tag) => 134 | format!("#{tag}"), 135 | ActorKind::InstanceRelay(instance) => 136 | instance.to_string(), 137 | ActorKind::LanguageRelay(language) => 138 | format!("in {language}"), 139 | }), 140 | icon: Some(activitypub::Media { 141 | media_type: Some("Image".to_string()), 142 | content_type: Some("image/jpeg".to_string()), 143 | url: "https://fedi.buzz/assets/favicon48.png".to_string(), 144 | }), 145 | inbox: self.uri(), 146 | endpoints: Some(activitypub::ActorEndpoints { 147 | shared_inbox: format!("https://{}/instance/{}", self.host, self.host), 148 | }), 149 | outbox: Some(format!("{}/outbox", self.uri())), 150 | public_key: activitypub::ActorPublicKey { 151 | id: self.key_id(), 152 | owner: Some(self.uri()), 153 | pem: pub_key.to_pem().unwrap(), 154 | }, 155 | preferred_username: Some(match &self.kind { 156 | ActorKind::TagRelay(tag) => 157 | format!("tag-{tag}"), 158 | ActorKind::InstanceRelay(instance) => 159 | format!("instance-{instance}"), 160 | ActorKind::LanguageRelay(language) => 161 | format!("language-{language}"), 162 | }), 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /INSTALL-UBUNTU.md: -------------------------------------------------------------------------------- 1 | # Objective 2 | This guide will provide you with a working relay to test and configure to your liking. 3 | 4 | If you are familiar with NixOS/Flakes, then [installing the NixOS module](https://github.com/astro/buzzrelay?tab=readme-ov-file#build) is by far the easier route! 5 | 6 | If you're not ready to take the NixOS plunge, here's another option to install the FediBuzz relay on server with a recent release of Ubuntu. 7 | 8 | ## Hardware 9 | The official buzzrelay is attached to hundreds of instances and has thousands of followers with a configuration similar to the requirements listed below. 10 | 11 | * 1 Core 12 | * 1 GB RAM 13 | 14 | If you're connecting to the fedi.buzz relay and perhaps one or two others on your own relay, this should be more than enough. 15 | 16 | One caveat here. FediBuzz has an option for individual users to utilize relays as well by "following" a relay actor, like `@tag-dogsofmastodon@relay.com`. 17 | 18 | If you promote this alternative route and many individuals start connecting to your relay, it will cause more outgoing traffic and queue processing, therefore increasing your hardware requirements. 19 | 20 | # Domain Name 21 | You'll need a domain or subdomain to run this application. For example, a subdomain of `https://relay.{yourdomain}`. 22 | 23 | # SSL Certificate 24 | An SSL certificate associated to your relay's domain name is required. This certificate should be added to your reverse proxy. 25 | 26 | Additionally, ensure the TLS configuration is properly set up to facilitate secure connections. 27 | 28 | Please refer to your reverse proxy's documentation for the specific steps required to complete this process. 29 | 30 | # Initial Server Installs 31 | These packages are required for rust / cargo to work. 32 | ``` 33 | sudo apt-get update 34 | sudo apt-get install pkg-config libssl-dev libsystemd-dev git cargo curl 35 | ``` 36 | 37 | ## Rust and related tooling install 38 | Ensure Rust is installed on your server. Ubuntu has a rustc installation included by default, but it is likely not the latest version. In addition, you may prefer to use rustup to manage your install. Check out [the official installation guide](https://www.rust-lang.org/tools/install). 39 | 40 | 41 | ## PostgreSQL 42 | A PostgreSQL database is needed for this application. This [installation guide](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-postgresql-on-ubuntu-22-04) will assist with the initial install. 43 | 44 | Create the relay user for the database. This command creates a user named relay and then prompts for a password. 45 | 46 | ``` 47 | sudo -u postgres createuser -P relay 48 | ``` 49 | 50 | Then create the database and grant all privileges to the relay user. 51 | 52 | ``` 53 | sudo -u postgres psql 54 | createdb -O relay buzzrelay 55 | 56 | \c buzzrelay 57 | 58 | GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO relay; 59 | GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO relay; 60 | GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public TO relay; 61 | 62 | ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON TABLES TO relay; 63 | ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON SEQUENCES TO relay; 64 | ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON FUNCTIONS TO relay; 65 | ``` 66 | 67 | ## Querying the database 68 | A cheat sheet for getting to the database. 69 | 70 | ``` 71 | psql -U relay -h localhost -d buzzrelay 72 | \c buzzrelay 73 | ``` 74 | 75 | # Redis 76 | It's not necessary to install this, it is not used by the core relay. Just comment out the associated lines in the YAML file. 77 | 78 | This was used if you are going to host the page shown at [https://fedi.buzz](https://fedi.buzz) which doesn't come with this relay configuration. 79 | 80 | ## Pull GitHub Repo 81 | ``` 82 | git clone https://github.com/astro/buzzrelay.git 83 | ``` 84 | 85 | # Update config.yaml 86 | 87 | Move into the project folder and open the config.yaml 88 | 89 | ```shell 90 | sudo vim config.yaml 91 | ``` 92 | 93 | ## Streams 94 | * Leave the fedibuzz stream as is. 95 | * Comment out the first example. 96 | * Change the last example to your instance's url and token. 97 | * Add as many others as desired. 98 | 99 | ## Additional filters for streams 100 | If you have a token for an instance that you are using to connect to a mastodon public stream, you're not limited to just the federated stream of all posts. If you want to get more granular, these [streaming timelines](https://docs.joinmastodon.org/methods/streaming/) work, too. 101 | 102 |
View additional filter details 103 | All of the items listed below have a /local/ version as well if you want to get REALLY granular and only pick up posts from the local instance. 104 | 105 | > This does not work for the default fedibuzz relay stream, only for mastodon servers for which you have an access token. 106 | 107 | **Public remote posts only - federated posts excluding local ones** 108 | 109 | You can also pass a "only_media" parameter in the querystring and get back only posts with some type of attachment (audio, image, or video) Cool! 110 | 111 | ```http 112 | GET /api/v1/streaming/public/remote?only_media={true|false}&access_token={yourAccessToken} HTTP/1.1 113 | ``` 114 | 115 | **Public posts with a specific hashtag** 116 | 117 | >This one does not has the only_media parameter unfortunately. 118 | 119 | ```http 120 | GET /api/v1/streaming/hashtag?tag={yourTag}&access_token={yourAccessToken} HTTP/1.1 121 | ``` 122 | 123 | **Watch a list for posts** 124 | For the user with the associated token, you can create a list of accounts and pass the list_id to this query. It will return only posts from those accounts. 125 | 126 | ```http 127 | GET /api/v1/streaming/list?list={yourListId}&access_token={yourAccessToken} HTTP/1.1 128 | ``` 129 |
130 | 131 | ### Hostname 132 | Set it to your domain. I used the sub-domain format of "relay.{yourdomain}" 133 | 134 | ### Listen Port 135 | Update if necessary for your proxy configuration. 136 | 137 | ### Private Key File 138 | Generate a new RSA key pair for signing ActivityPub messages. Note using this command also sets the appropriate permissions values. 139 | 140 | Run these commands from the root of the project. 141 | 142 | ```bash 143 | openssl genrsa -out private-key.pem 4096 144 | openssl rsa -in private-key.pem -pubout -out public-key.pem 145 | ``` 146 | 147 | ## PostgreSQL Password 148 | I used the default user and dbname listed in the config file. Update the password as needed. 149 | 150 | # Build it 151 | From the root of the buzzrelay project, with the config.yaml finalized, run the following. 152 | 153 | ``` 154 | cargo build --release 155 | ``` 156 | 157 | This creates a compiled version in the target/release folder. 158 | 159 | From the root of the project, you can run this command to start up the app: 160 | 161 | ``` 162 | cargo run --release config.yaml 163 | ``` 164 | 165 | If you see redis errors, be sure to comment out those lines in the config.yaml - it is NOT needed. 166 | 167 | With the fedi relay public stream enabled, I did see the following error stream quite often, showing that the uri is missing, which it is. 168 | 169 | ``` 170 | 2024-03-23T03:39:34.773184Z TRACE buzzrelay::relay: data: {"created_at":"2024-03-23T03:39:33.020Z","url":"https://some.instance/notes/9r73vj18yk","content":"

@some.user Some Content​

","account":{"username":"some.user","display_name:":"some.display.name","url":"https://some.instance/@some.user","bot":true},"tags":[],"sensitive":false,"mentions":[],"language":"ja","media_attachments":[],"reblog":null} 171 | 2024-03-23T03:39:48.745870Z ERROR buzzrelay::relay: parse error: missing field `uri` at line 1 column 746 172 | ``` 173 | 174 | However, even with that error, plenty of content is getting pushed to my instance. 175 | 176 | # Try it out 177 | Check the homepage of your new relay for instructions on how to add your desired entries to a fediverse server and start pulling in posts. 178 | 179 | You should see entries being added to your federated timeline. 180 | 181 | You've got a basic working relay to test with. Congratulations! 🎉 -------------------------------------------------------------------------------- /src/relay.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::Arc, collections::{HashSet, HashMap}, time::{Duration, Instant}}; 2 | use futures::{channel::mpsc::{channel, Sender}, StreamExt}; 3 | use metrics::{counter, histogram}; 4 | use serde::Deserialize; 5 | use serde_json::json; 6 | use sigh::PrivateKey; 7 | use tokio::sync::mpsc::Receiver; 8 | use crate::{send, actor, state::State}; 9 | 10 | #[derive(Deserialize)] 11 | struct Post<'a> { 12 | pub url: Option<&'a str>, 13 | pub uri: &'a str, 14 | pub tags: Option>>, 15 | pub language: Option<&'a str>, 16 | } 17 | 18 | impl Post<'_> { 19 | pub fn host(&self) -> Option { 20 | reqwest::Url::parse(self.url?) 21 | .ok() 22 | .and_then(|url| url.domain() 23 | .map(str::to_lowercase) 24 | ) 25 | } 26 | 27 | pub fn tags(&self) -> Vec { 28 | match &self.tags { 29 | None => 30 | vec![], 31 | Some(tags) => 32 | tags.iter() 33 | .map(|tag| tag.name.to_string()) 34 | .collect() 35 | } 36 | } 37 | 38 | fn relay_target_kinds(&self) -> impl Iterator { 39 | self.host() 40 | .into_iter() 41 | .map(actor::ActorKind::InstanceRelay) 42 | .chain( 43 | self.tags() 44 | .into_iter() 45 | .flat_map(|ref s| { 46 | // Don't handle the empty hashtag `#` 47 | if s.is_empty() { 48 | return vec![]; 49 | } 50 | 51 | let actor1 = actor::ActorKind::from_tag(s); 52 | 53 | // Distribute hashtags that end in a date to 54 | // followers of the hashtag with the date 55 | // stripped. Example: #dd1302 -> #dd 56 | let mut first_trailing_digit = 0; 57 | let mut scanning_digits = false; 58 | for (pos, c) in s.char_indices() { 59 | if char::is_digit(c, 10) { 60 | if ! scanning_digits { 61 | first_trailing_digit = pos; 62 | scanning_digits = true; 63 | } 64 | } else { 65 | scanning_digits = false; 66 | } 67 | } 68 | if scanning_digits && first_trailing_digit > 0 { 69 | let tag = &s[..first_trailing_digit]; 70 | let actor2 = actor::ActorKind::from_tag(tag); 71 | vec![actor1, actor2] 72 | } else { 73 | vec![actor1] 74 | } 75 | }) 76 | ) 77 | .chain( 78 | self.language 79 | .and_then(actor::ActorKind::from_language) 80 | ) 81 | } 82 | 83 | pub fn relay_targets(&self, hostname: Arc) -> impl Iterator { 84 | self.relay_target_kinds() 85 | .map(move |kind| actor::Actor { 86 | host: hostname.clone(), 87 | kind, 88 | }) 89 | } 90 | } 91 | 92 | #[derive(Deserialize)] 93 | struct Tag<'a> { 94 | pub name: &'a str, 95 | } 96 | 97 | struct Job { 98 | post_url: Arc, 99 | actor_id: Arc, 100 | body: Arc>, 101 | key_id: String, 102 | private_key: Arc, 103 | inbox_url: reqwest::Url, 104 | } 105 | 106 | fn spawn_worker(client: Arc) -> Sender { 107 | let (tx, mut rx) = channel(512); 108 | 109 | tokio::spawn(async move { 110 | let mut errors = 0u32; 111 | let mut last_request = None; 112 | 113 | while let Some(Job { post_url, actor_id, key_id, private_key, body, inbox_url }) = rx.next().await { 114 | if errors > 0 && last_request.is_some_and(|last_request: Instant| 115 | last_request.elapsed() < Duration::from_secs(10) * errors 116 | ) { 117 | // there have been errors, skip for time proportional 118 | // to the number of subsequent errors 119 | tracing::trace!("skip {} from {} to {}", post_url, actor_id, inbox_url); 120 | continue; 121 | } 122 | 123 | tracing::debug!("relay {} from {} to {}", post_url, actor_id, inbox_url); 124 | last_request = Some(Instant::now()); 125 | if let Err(e) = send::send_raw( 126 | &client, inbox_url.as_str(), 127 | &key_id, &private_key, body 128 | ).await { 129 | tracing::error!("relay::send {:?}", e); 130 | errors = errors.saturating_add(1); 131 | } else { 132 | // success 133 | errors = 0; 134 | systemd::daemon::notify( 135 | false, [ 136 | (systemd::daemon::STATE_WATCHDOG, "1") 137 | ].iter() 138 | ).unwrap(); 139 | } 140 | } 141 | 142 | panic!("Worker dead"); 143 | }); 144 | 145 | tx 146 | } 147 | 148 | pub fn spawn( 149 | state: State, 150 | mut stream_rx: Receiver 151 | ) { 152 | tokio::spawn(async move { 153 | let mut workers = HashMap::new(); 154 | 155 | while let Some(data) = stream_rx.recv().await { 156 | let t1 = Instant::now(); 157 | let post: Post = match serde_json::from_str(&data) { 158 | Ok(post) => post, 159 | Err(e) => { 160 | tracing::error!("parse error: {}", e); 161 | tracing::trace!("data: {}", data); 162 | continue; 163 | } 164 | }; 165 | let post_url = if let Some(url) = post.url { 166 | Arc::new(url.to_string()) 167 | } else { 168 | // skip reposts 169 | counter!("relay_posts_total", "action" => "skip") 170 | .increment(1); 171 | continue; 172 | }; 173 | let mut seen_actors = HashSet::new(); 174 | let mut seen_inboxes = HashSet::new(); 175 | let published = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true); 176 | for actor in post.relay_targets(state.hostname.clone()) { 177 | if seen_actors.contains(&actor) { 178 | continue; 179 | } 180 | 181 | let actor_id = Arc::new(actor.uri()); 182 | let announce_id = format!("https://{}/announce/{}", state.hostname, urlencoding::encode(&post_url)); 183 | let body = json!({ 184 | "@context": "https://www.w3.org/ns/activitystreams", 185 | "type": "Announce", 186 | "actor": *actor_id, 187 | "published": &published, 188 | "to": ["https://www.w3.org/ns/activitystreams#Public"], 189 | "object": &post.uri, 190 | "id": announce_id, 191 | }); 192 | let Ok(post_url_url) = reqwest::Url::parse(&post_url) else { continue; }; 193 | let body = Arc::new( 194 | serde_json::to_vec(&body) 195 | .unwrap() 196 | ); 197 | for inbox in state.database.get_following_inboxes(&actor_id).await.unwrap() { 198 | let Ok(inbox_url) = reqwest::Url::parse(&inbox) else { continue; }; 199 | 200 | // Avoid duplicate processing. 201 | if seen_inboxes.contains(&inbox) { 202 | continue; 203 | } 204 | seen_inboxes.insert(inbox); 205 | 206 | // Prevent relaying back to the originating instance. 207 | if inbox_url.host_str() == post_url_url.host_str() { 208 | continue; 209 | } 210 | 211 | // Lookup/create worker queue per inbox. 212 | let tx = workers.entry(inbox_url.host_str().unwrap_or("").to_string()) 213 | .or_insert_with(|| spawn_worker(state.client.clone())); 214 | // Create queue item. 215 | let job = Job { 216 | post_url: post_url.clone(), 217 | actor_id: actor_id.clone(), 218 | body: body.clone(), 219 | key_id: actor.key_id(), 220 | private_key: state.priv_key.clone(), 221 | inbox_url, 222 | }; 223 | // Enqueue job for worker. 224 | let _ = tx.try_send(job); 225 | } 226 | 227 | seen_actors.insert(actor); 228 | } 229 | if seen_inboxes.is_empty() { 230 | counter!("relay_posts_total", "action" => "no_relay") 231 | .increment(1); 232 | } else { 233 | counter!("relay_posts_total", "action" => "relay") 234 | .increment(1); 235 | } 236 | let t2 = Instant::now(); 237 | histogram!("relay_post_duration").record(t2 - t1); 238 | } 239 | }); 240 | } 241 | 242 | #[cfg(test)] 243 | mod test { 244 | use super::*; 245 | use actor::ActorKind; 246 | 247 | #[test] 248 | fn post_relay_kind() { 249 | let post = Post { 250 | url: Some("http://example.com/post/1"), 251 | uri: "http://example.com/post/1", 252 | tags: Some(vec![Tag { 253 | name: "foo", 254 | }]), 255 | language: Some("en"), 256 | }; 257 | let mut kinds = post.relay_target_kinds(); 258 | assert_eq!(kinds.next(), Some(ActorKind::InstanceRelay("example.com".to_string()))); 259 | assert_eq!(kinds.next(), Some(ActorKind::TagRelay("foo".to_string()))); 260 | assert_eq!(kinds.next(), Some(ActorKind::LanguageRelay("en".to_string()))); 261 | assert_eq!(kinds.next(), None); 262 | } 263 | 264 | #[test] 265 | fn post_relay_kind_empty() { 266 | let post = Post { 267 | url: Some("http://example.com/post/1"), 268 | uri: "http://example.com/post/1", 269 | tags: Some(vec![Tag { 270 | name: "", 271 | }]), 272 | language: None, 273 | }; 274 | let mut kinds = post.relay_target_kinds(); 275 | assert_eq!(kinds.next(), Some(ActorKind::InstanceRelay("example.com".to_string()))); 276 | assert_eq!(kinds.next(), None); 277 | } 278 | 279 | #[test] 280 | fn post_relay_kind_numeric() { 281 | let post = Post { 282 | url: Some("http://example.com/post/1"), 283 | uri: "http://example.com/post/1", 284 | tags: Some(vec![Tag { 285 | name: "23", 286 | }]), 287 | language: None, 288 | }; 289 | let mut kinds = post.relay_target_kinds(); 290 | assert_eq!(kinds.next(), Some(ActorKind::InstanceRelay("example.com".to_string()))); 291 | assert_eq!(kinds.next(), Some(ActorKind::TagRelay("23".to_string()))); 292 | assert_eq!(kinds.next(), None); 293 | } 294 | 295 | #[test] 296 | fn post_relay_kind_date() { 297 | let post = Post { 298 | url: Some("http://example.com/post/1"), 299 | uri: "http://example.com/post/1", 300 | tags: Some(vec![Tag { 301 | name: "dd1302", 302 | }]), 303 | language: None, 304 | }; 305 | let mut kinds = post.relay_target_kinds(); 306 | assert_eq!(kinds.next(), Some(ActorKind::InstanceRelay("example.com".to_string()))); 307 | assert_eq!(kinds.next(), Some(ActorKind::TagRelay("dd1302".to_string()))); 308 | assert_eq!(kinds.next(), Some(ActorKind::TagRelay("dd".to_string()))); 309 | assert_eq!(kinds.next(), None); 310 | } 311 | 312 | #[test] 313 | fn post_relay_kind_ja() { 314 | let post = Post { 315 | url: Some("http://example.com/post/1"), 316 | uri: "http://example.com/post/1", 317 | tags: Some(vec![Tag { 318 | name: "スコティッシュ・フォールド・ロングヘアー", 319 | }]), 320 | language: Some("ja"), 321 | }; 322 | let mut kinds = post.relay_target_kinds(); 323 | assert_eq!(kinds.next(), Some(ActorKind::InstanceRelay("example.com".to_string()))); 324 | assert_eq!(kinds.next(), Some(ActorKind::TagRelay("sukoteitusiyuhuorudoronguhea".to_string()))); 325 | assert_eq!(kinds.next(), Some(ActorKind::LanguageRelay("ja".to_string()))); 326 | assert_eq!(kinds.next(), None); 327 | } 328 | 329 | #[test] 330 | fn post_relay_language_long() { 331 | let post = Post { 332 | url: Some("http://example.com/post/1"), 333 | uri: "http://example.com/post/1", 334 | tags: None, 335 | language: Some("de_CH"), 336 | }; 337 | let mut kinds = post.relay_target_kinds(); 338 | assert_eq!(kinds.next(), Some(ActorKind::InstanceRelay("example.com".to_string()))); 339 | assert_eq!(kinds.next(), Some(ActorKind::LanguageRelay("de".to_string()))); 340 | assert_eq!(kinds.next(), None); 341 | } 342 | 343 | #[test] 344 | fn post_relay_language_invalid() { 345 | let post = Post { 346 | url: Some("http://example.com/post/1"), 347 | uri: "http://example.com/post/1", 348 | tags: None, 349 | language: Some("23q"), 350 | }; 351 | let mut kinds = post.relay_target_kinds(); 352 | assert_eq!(kinds.next(), Some(ActorKind::InstanceRelay("example.com".to_string()))); 353 | assert_eq!(kinds.next(), None); 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use axum::{ 2 | extract::{Path, Query}, 3 | http::StatusCode, 4 | response::{IntoResponse, Response}, 5 | routing::get, Json, Router, 6 | }; 7 | use tower_http::services::ServeDir; 8 | use metrics::counter; 9 | use metrics_util::MetricKindMask; 10 | use metrics_exporter_prometheus::PrometheusBuilder; 11 | use serde_json::json; 12 | use std::{net::SocketAddr, time::Duration, collections::HashMap}; 13 | use std::{panic, process}; 14 | use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; 15 | use reqwest::Url; 16 | 17 | mod error; 18 | mod config; 19 | mod state; 20 | mod actor; 21 | mod db; 22 | mod digest; 23 | mod fetch; 24 | mod send; 25 | mod stream; 26 | mod relay; 27 | mod activitypub; 28 | mod actor_cache; 29 | mod endpoint; 30 | 31 | use actor::Actor; 32 | use state::State; 33 | 34 | 35 | fn track_request(method: &'static str, controller: &'static str, result: &'static str) { 36 | counter!("api_http_requests_total", "controller" => controller, "method" => method, "result" => result) 37 | .increment(1); 38 | } 39 | 40 | async fn webfinger( 41 | Query(params): Query>, 42 | ) -> Response { 43 | let Some(resource) = params.get("resource") else { 44 | track_request("GET", "webfinger", "invalid"); 45 | return StatusCode::NOT_FOUND.into_response(); 46 | }; 47 | let Some(target) = Actor::from_uri(resource) else { 48 | track_request("GET", "webfinger", "not_found"); 49 | return StatusCode::NOT_FOUND.into_response(); 50 | }; 51 | track_request("GET", "webfinger", "found"); 52 | Json(json!({ 53 | "subject": &resource, 54 | "aliases": &[ 55 | target.uri(), 56 | ], 57 | "links": &[json!({ 58 | "rel": "self", 59 | "type": "application/activity+json", 60 | "href": target.uri(), 61 | })], 62 | })).into_response() 63 | } 64 | 65 | async fn get_tag_actor( 66 | axum::extract::State(state): axum::extract::State, 67 | Path(tag): Path 68 | ) -> Response { 69 | track_request("GET", "actor", "tag"); 70 | let target = actor::Actor { 71 | host: state.hostname.clone(), 72 | kind: actor::ActorKind::from_tag(&tag), 73 | }; 74 | target.as_activitypub(&state.pub_key) 75 | .into_response() 76 | } 77 | 78 | async fn get_instance_actor( 79 | axum::extract::State(state): axum::extract::State, 80 | Path(instance): Path 81 | ) -> Response { 82 | track_request("GET", "actor", "instance"); 83 | let target = actor::Actor { 84 | host: state.hostname.clone(), 85 | kind: actor::ActorKind::InstanceRelay(instance.to_lowercase()), 86 | }; 87 | target.as_activitypub(&state.pub_key) 88 | .into_response() 89 | } 90 | 91 | async fn get_language_actor( 92 | axum::extract::State(state): axum::extract::State, 93 | Path(language): Path 94 | ) -> Response { 95 | track_request("GET", "actor", "language"); 96 | let Some(kind) = actor::ActorKind::from_language(&language) else { 97 | return StatusCode::NOT_FOUND.into_response(); 98 | }; 99 | let target = actor::Actor { 100 | host: state.hostname.clone(), 101 | kind, 102 | }; 103 | target.as_activitypub(&state.pub_key) 104 | .into_response() 105 | } 106 | 107 | async fn post_tag_relay( 108 | axum::extract::State(state): axum::extract::State, 109 | Path(tag): Path, 110 | endpoint: endpoint::Endpoint<'_> 111 | ) -> Response { 112 | let target = actor::Actor { 113 | host: state.hostname.clone(), 114 | kind: actor::ActorKind::from_tag(&tag), 115 | }; 116 | post_relay(state, endpoint, target).await 117 | } 118 | 119 | async fn post_instance_relay( 120 | axum::extract::State(state): axum::extract::State, 121 | Path(instance): Path, 122 | endpoint: endpoint::Endpoint<'_> 123 | ) -> Response { 124 | let target = actor::Actor { 125 | host: state.hostname.clone(), 126 | kind: actor::ActorKind::InstanceRelay(instance.to_lowercase()), 127 | }; 128 | post_relay(state, endpoint, target).await 129 | } 130 | 131 | async fn post_language_relay( 132 | axum::extract::State(state): axum::extract::State, 133 | Path(language): Path, 134 | endpoint: endpoint::Endpoint<'_> 135 | ) -> Response { 136 | let Some(kind) = actor::ActorKind::from_language(&language) else { 137 | return StatusCode::NOT_FOUND.into_response(); 138 | }; 139 | let target = actor::Actor { 140 | host: state.hostname.clone(), 141 | kind, 142 | }; 143 | post_relay(state, endpoint, target).await 144 | } 145 | 146 | async fn post_relay( 147 | state: State, 148 | endpoint: endpoint::Endpoint<'_>, 149 | mut target: actor::Actor 150 | ) -> Response { 151 | if let Some((redis, in_topic)) = &state.redis { 152 | if let Ok(data) = serde_json::to_vec(&endpoint.payload) { 153 | if let Err(e) = redis::Cmd::publish(in_topic.as_ref(), data) 154 | .query_async::(&mut redis.clone()) 155 | .await 156 | { 157 | tracing::error!("redis publish: {}", e); 158 | } 159 | } 160 | } 161 | 162 | let remote_actor = endpoint.remote_actor(&state.client, &state.actor_cache, target.key_id(), state.priv_key.clone()) 163 | .await 164 | .map_err(|e| { 165 | track_request("POST", "relay", "bad_actor"); 166 | tracing::error!("post_relay bad actor: {e:?}"); 167 | e 168 | }); 169 | 170 | let action = match serde_json::from_value::>(endpoint.payload.clone()) { 171 | Ok(action) => action, 172 | Err(e) => { 173 | track_request("POST", "relay", "bad_action"); 174 | tracing::error!("post_relay bad action: {e:?}"); 175 | return ( 176 | StatusCode::BAD_REQUEST, 177 | format!("Bad action: {e:?}") 178 | ).into_response(); 179 | } 180 | }; 181 | let object_type = action.object.as_ref() 182 | .and_then(|object| object.get("type").cloned()) 183 | .and_then(|object_type| object_type.as_str().map(std::string::ToString::to_string)); 184 | 185 | if action.action_type == "Follow" { 186 | let Ok(remote_actor) = remote_actor else { 187 | return (StatusCode::BAD_REQUEST, "Invalid actor").into_response(); 188 | }; 189 | if let Some(action_target) = action.object.and_then(|object| Actor::from_object(&object)) { 190 | if action_target.host == state.hostname { 191 | // A sharedInbox receives the actual follow target in the 192 | // `object` field. 193 | target = action_target; 194 | } 195 | } 196 | let priv_key = state.priv_key.clone(); 197 | let client = state.client.clone(); 198 | tokio::spawn(async move { 199 | let accept_id = format!( 200 | "https://{}/activity/accept/{}/{}", 201 | state.hostname, 202 | urlencoding::encode(&target.uri()), 203 | urlencoding::encode(&remote_actor.inbox), 204 | ); 205 | let accept = activitypub::Action { 206 | jsonld_context: serde_json::Value::String("https://www.w3.org/ns/activitystreams".to_string()), 207 | action_type: "Accept".to_string(), 208 | actor: target.uri(), 209 | to: Some(json!(remote_actor.id.clone())), 210 | id: accept_id, 211 | object: Some(endpoint.payload), 212 | }; 213 | let result = send::send( 214 | client.as_ref(), &remote_actor.inbox, 215 | &target.key_id(), 216 | &priv_key, 217 | &accept, 218 | ).await; 219 | match result { 220 | Ok(()) => { 221 | match state.database.add_follow( 222 | &remote_actor.id, 223 | &remote_actor.inbox, 224 | &target.uri(), 225 | ).await { 226 | Ok(()) => { 227 | track_request("POST", "relay", "follow"); 228 | } 229 | Err(e) => { 230 | // duplicate key constraint 231 | tracing::error!("add_follow: {}", e); 232 | track_request("POST", "relay", "follow_error"); 233 | } 234 | } 235 | } 236 | Err(e) => { 237 | tracing::error!("post accept: {}", e); 238 | track_request("POST", "relay", "follow_accept_error"); 239 | } 240 | } 241 | }); 242 | 243 | (StatusCode::ACCEPTED, 244 | [("content-type", "application/activity+json")], 245 | "{}" 246 | ).into_response() 247 | } else if action.action_type == "Undo" && object_type == Some("Follow".to_string()) { 248 | let Ok(remote_actor) = remote_actor else { 249 | return (StatusCode::BAD_REQUEST, "Invalid actor").into_response(); 250 | }; 251 | if let Some(action_target) = action.object 252 | .and_then(|object| object.get("object") 253 | .and_then(Actor::from_object)) 254 | { 255 | if action_target.host == state.hostname { 256 | // A sharedInbox receives the actual follow target in the 257 | // `object` field. 258 | target = action_target; 259 | } 260 | } 261 | match state.database.del_follow( 262 | &remote_actor.id, 263 | &target.uri(), 264 | ).await { 265 | Ok(()) => { 266 | track_request("POST", "relay", "unfollow"); 267 | (StatusCode::ACCEPTED, 268 | [("content-type", "application/activity+json")], 269 | "{}" 270 | ).into_response() 271 | } 272 | Err(e) => { 273 | tracing::error!("del_follow: {}", e); 274 | track_request("POST", "relay", "unfollow_error"); 275 | (StatusCode::INTERNAL_SERVER_ERROR, 276 | format!("{e}") 277 | ).into_response() 278 | } 279 | } 280 | } else { 281 | track_request("POST", "relay", "unrecognized"); 282 | (StatusCode::ACCEPTED, 283 | [("content-type", "application/activity+json")], 284 | "{}" 285 | ).into_response() 286 | } 287 | } 288 | 289 | /// An empty `ActivityStreams` outbox just to satisfy the spec 290 | async fn outbox() -> Response { 291 | Json(json!({ 292 | "@context": "https://www.w3.org/ns/activitystreams", 293 | "summary": "buzzrelay stub outbox", 294 | "type": "OrderedCollection", 295 | "totalItems": 0, 296 | "orderedItems": [] 297 | })).into_response() 298 | } 299 | 300 | async fn nodeinfo(axum::extract::State(state): axum::extract::State) -> Response { 301 | let follows_count = state.database.get_follows_count() 302 | .await 303 | .unwrap_or(0); 304 | let followers_count = state.database.get_followers_count() 305 | .await 306 | .unwrap_or(0); 307 | 308 | Json(json!({ 309 | "version": "2.1", 310 | "software": { 311 | "name": env!("CARGO_PKG_NAME"), 312 | "version": env!("CARGO_PKG_VERSION"), 313 | "repository": env!("CARGO_PKG_REPOSITORY"), 314 | "homepage": env!("CARGO_PKG_HOMEPAGE"), 315 | }, 316 | "protocols": ["activitypub"], 317 | "services": { 318 | "inbound": [], 319 | "outbound": [] 320 | }, 321 | "openRegistrations": false, 322 | "usage": { 323 | "users": { 324 | "total": 0, 325 | "activeHalfyear": followers_count, 326 | "activeMonth": 0, 327 | }, 328 | "localPosts": follows_count, 329 | "localComments": 0 330 | }, 331 | "metadata": { 332 | "rust_version": env!("CARGO_PKG_RUST_VERSION"), 333 | }, 334 | "links": vec![ 335 | json!({ 336 | "rel": "http://nodeinfo.diaspora.software/ns/schema/2.1", 337 | "href": format!("https://{}/.well-known/nodeinfo", state.hostname), 338 | }), 339 | ], 340 | })).into_response() 341 | } 342 | 343 | /// Expected by `AodeRelay` 344 | async fn instanceinfo() -> Response { 345 | Json(json!({ 346 | "title": env!("CARGO_PKG_NAME"), 347 | "description": "#FediBuzz Relay", 348 | "version": env!("CARGO_PKG_VERSION"), 349 | "registrations": false, 350 | "default_approval": false, 351 | })).into_response() 352 | } 353 | 354 | 355 | #[tokio::main] 356 | async fn main() { 357 | exit_on_panic(); 358 | 359 | tracing_subscriber::registry() 360 | .with( 361 | tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| { 362 | "buzzrelay=trace,tower_http=trace,axum=trace".into() 363 | }), 364 | ) 365 | .with(tracing_subscriber::fmt::layer()) 366 | .init(); 367 | 368 | let recorder = PrometheusBuilder::new() 369 | .add_global_label("application", env!("CARGO_PKG_NAME")) 370 | .idle_timeout(MetricKindMask::ALL, Some(Duration::from_secs(600))) 371 | .install_recorder() 372 | .unwrap(); 373 | 374 | let config = config::Config::load( 375 | &std::env::args().nth(1) 376 | .expect("Call with config.yaml") 377 | ); 378 | let database = db::Database::connect(&config.db).await; 379 | let mut redis = None; 380 | if let Some(redis_config) = config.redis.clone() { 381 | let mut redis_url = Url::parse(&redis_config.connection) 382 | .expect("redis.connection"); 383 | let redis_password = std::fs::read_to_string(redis_config.password_file) 384 | .expect("redis.password_file"); 385 | redis_url.set_password(Some(&redis_password)).unwrap(); 386 | let client = redis::Client::open(redis_url) 387 | .expect("redis::Client"); 388 | let manager = redis::aio::ConnectionManager::new(client) 389 | .await 390 | .expect("redis::Client"); 391 | redis = Some((manager, redis_config.in_topic)); 392 | } 393 | let client = reqwest::Client::builder() 394 | .timeout(Duration::from_secs(5)) 395 | .user_agent(format!( 396 | "{}/{} (+https://{})", 397 | env!("CARGO_PKG_NAME"), 398 | env!("CARGO_PKG_VERSION"), 399 | config.hostname, 400 | )) 401 | .pool_max_idle_per_host(1) 402 | .pool_idle_timeout(Some(Duration::from_secs(5))) 403 | .build() 404 | .unwrap(); 405 | let state = State::new(config.clone(), database, redis, client); 406 | 407 | let stream_rx = stream::spawn(config.streams.clone().into_iter()); 408 | relay::spawn(state.clone(), stream_rx); 409 | 410 | let app = Router::new() 411 | .route("/tag/{tag}", get(get_tag_actor).post(post_tag_relay)) 412 | .route("/instance/{instance}", get(get_instance_actor).post(post_instance_relay)) 413 | .route("/language/{language}", get(get_language_actor).post(post_language_relay)) 414 | .route("/tag/{tag}/outbox", get(outbox)) 415 | .route("/instance/{instance}/outbox", get(outbox)) 416 | .route("/language/{language}/outbox", get(outbox)) 417 | .route("/.well-known/webfinger", get(webfinger)) 418 | .route("/.well-known/nodeinfo", get(nodeinfo)) 419 | .route("/api/v1/instance", get(instanceinfo)) 420 | .route("/metrics", get(|| async move { 421 | recorder.render().into_response() 422 | })) 423 | .with_state(state) 424 | .fallback_service(ServeDir::new("static")); 425 | 426 | let addr = SocketAddr::from(([127, 0, 0, 1], config.listen_port)); 427 | let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); 428 | let server = axum::serve(listener, app.into_make_service()); 429 | 430 | tracing::info!("serving on {}", addr); 431 | systemd::daemon::notify(false, [(systemd::daemon::STATE_READY, "1")].iter()) 432 | .unwrap(); 433 | server.await 434 | .unwrap(); 435 | } 436 | 437 | fn exit_on_panic() { 438 | let orig_hook = panic::take_hook(); 439 | panic::set_hook(Box::new(move |panic_info| { 440 | // invoke the default handler and exit the process 441 | orig_hook(panic_info); 442 | process::exit(1); 443 | })); 444 | } 445 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published by 637 | the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.8.12" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" 10 | dependencies = [ 11 | "cfg-if", 12 | "once_cell", 13 | "version_check", 14 | "zerocopy", 15 | ] 16 | 17 | [[package]] 18 | name = "aho-corasick" 19 | version = "1.1.4" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" 22 | dependencies = [ 23 | "memchr", 24 | ] 25 | 26 | [[package]] 27 | name = "allocator-api2" 28 | version = "0.2.21" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 31 | 32 | [[package]] 33 | name = "android_system_properties" 34 | version = "0.1.5" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 37 | dependencies = [ 38 | "libc", 39 | ] 40 | 41 | [[package]] 42 | name = "anyhow" 43 | version = "1.0.100" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 46 | 47 | [[package]] 48 | name = "arc-swap" 49 | version = "1.7.1" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" 52 | 53 | [[package]] 54 | name = "async-trait" 55 | version = "0.1.89" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" 58 | dependencies = [ 59 | "proc-macro2", 60 | "quote", 61 | "syn", 62 | ] 63 | 64 | [[package]] 65 | name = "atomic-waker" 66 | version = "1.1.2" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 69 | 70 | [[package]] 71 | name = "autocfg" 72 | version = "1.5.0" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 75 | 76 | [[package]] 77 | name = "aws-lc-rs" 78 | version = "1.15.0" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "5932a7d9d28b0d2ea34c6b3779d35e3dd6f6345317c34e73438c4f1f29144151" 81 | dependencies = [ 82 | "aws-lc-sys", 83 | "zeroize", 84 | ] 85 | 86 | [[package]] 87 | name = "aws-lc-sys" 88 | version = "0.33.0" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "1826f2e4cfc2cd19ee53c42fbf68e2f81ec21108e0b7ecf6a71cf062137360fc" 91 | dependencies = [ 92 | "bindgen", 93 | "cc", 94 | "cmake", 95 | "dunce", 96 | "fs_extra", 97 | ] 98 | 99 | [[package]] 100 | name = "axum" 101 | version = "0.8.7" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" 104 | dependencies = [ 105 | "axum-core", 106 | "bytes", 107 | "form_urlencoded", 108 | "futures-util", 109 | "http", 110 | "http-body", 111 | "http-body-util", 112 | "hyper", 113 | "hyper-util", 114 | "itoa", 115 | "matchit", 116 | "memchr", 117 | "mime", 118 | "percent-encoding", 119 | "pin-project-lite", 120 | "serde_core", 121 | "serde_json", 122 | "serde_path_to_error", 123 | "serde_urlencoded", 124 | "sync_wrapper", 125 | "tokio", 126 | "tower", 127 | "tower-layer", 128 | "tower-service", 129 | "tracing", 130 | ] 131 | 132 | [[package]] 133 | name = "axum-core" 134 | version = "0.5.5" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" 137 | dependencies = [ 138 | "bytes", 139 | "futures-core", 140 | "http", 141 | "http-body", 142 | "http-body-util", 143 | "mime", 144 | "pin-project-lite", 145 | "sync_wrapper", 146 | "tower-layer", 147 | "tower-service", 148 | "tracing", 149 | ] 150 | 151 | [[package]] 152 | name = "backon" 153 | version = "1.6.0" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" 156 | dependencies = [ 157 | "fastrand", 158 | ] 159 | 160 | [[package]] 161 | name = "base64" 162 | version = "0.13.1" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 165 | 166 | [[package]] 167 | name = "base64" 168 | version = "0.22.1" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 171 | 172 | [[package]] 173 | name = "bindgen" 174 | version = "0.72.1" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" 177 | dependencies = [ 178 | "bitflags", 179 | "cexpr", 180 | "clang-sys", 181 | "itertools", 182 | "log", 183 | "prettyplease", 184 | "proc-macro2", 185 | "quote", 186 | "regex", 187 | "rustc-hash", 188 | "shlex", 189 | "syn", 190 | ] 191 | 192 | [[package]] 193 | name = "bitflags" 194 | version = "2.10.0" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 197 | 198 | [[package]] 199 | name = "block-buffer" 200 | version = "0.10.4" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 203 | dependencies = [ 204 | "generic-array", 205 | ] 206 | 207 | [[package]] 208 | name = "build-env" 209 | version = "0.3.1" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "1522ac6ee801a11bf9ef3f80403f4ede6eb41291fac3dde3de09989679305f25" 212 | 213 | [[package]] 214 | name = "bumpalo" 215 | version = "3.19.0" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 218 | 219 | [[package]] 220 | name = "buzzrelay" 221 | version = "0.1.0" 222 | dependencies = [ 223 | "axum", 224 | "chrono", 225 | "deunicode", 226 | "eventsource-stream", 227 | "futures", 228 | "http", 229 | "http_digest_headers", 230 | "httpdate", 231 | "lru", 232 | "metrics", 233 | "metrics-exporter-prometheus", 234 | "metrics-util", 235 | "redis", 236 | "reqwest", 237 | "serde", 238 | "serde_json", 239 | "serde_yaml", 240 | "sigh", 241 | "systemd", 242 | "thiserror 2.0.17", 243 | "tokio", 244 | "tokio-postgres", 245 | "tower-http", 246 | "tracing", 247 | "tracing-subscriber", 248 | "urlencoding", 249 | ] 250 | 251 | [[package]] 252 | name = "byteorder" 253 | version = "1.5.0" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 256 | 257 | [[package]] 258 | name = "bytes" 259 | version = "1.11.0" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" 262 | 263 | [[package]] 264 | name = "cc" 265 | version = "1.2.46" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" 268 | dependencies = [ 269 | "find-msvc-tools", 270 | "jobserver", 271 | "libc", 272 | "shlex", 273 | ] 274 | 275 | [[package]] 276 | name = "cexpr" 277 | version = "0.6.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 280 | dependencies = [ 281 | "nom", 282 | ] 283 | 284 | [[package]] 285 | name = "cfg-if" 286 | version = "1.0.4" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 289 | 290 | [[package]] 291 | name = "cfg_aliases" 292 | version = "0.2.1" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 295 | 296 | [[package]] 297 | name = "chrono" 298 | version = "0.4.42" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" 301 | dependencies = [ 302 | "iana-time-zone", 303 | "js-sys", 304 | "num-traits", 305 | "wasm-bindgen", 306 | "windows-link", 307 | ] 308 | 309 | [[package]] 310 | name = "clang-sys" 311 | version = "1.8.1" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 314 | dependencies = [ 315 | "glob", 316 | "libc", 317 | "libloading", 318 | ] 319 | 320 | [[package]] 321 | name = "cmake" 322 | version = "0.1.54" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" 325 | dependencies = [ 326 | "cc", 327 | ] 328 | 329 | [[package]] 330 | name = "combine" 331 | version = "4.6.7" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" 334 | dependencies = [ 335 | "bytes", 336 | "futures-core", 337 | "memchr", 338 | "pin-project-lite", 339 | "tokio", 340 | "tokio-util", 341 | ] 342 | 343 | [[package]] 344 | name = "core-foundation" 345 | version = "0.9.4" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 348 | dependencies = [ 349 | "core-foundation-sys", 350 | "libc", 351 | ] 352 | 353 | [[package]] 354 | name = "core-foundation" 355 | version = "0.10.1" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" 358 | dependencies = [ 359 | "core-foundation-sys", 360 | "libc", 361 | ] 362 | 363 | [[package]] 364 | name = "core-foundation-sys" 365 | version = "0.8.7" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 368 | 369 | [[package]] 370 | name = "cpufeatures" 371 | version = "0.2.17" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 374 | dependencies = [ 375 | "libc", 376 | ] 377 | 378 | [[package]] 379 | name = "critical-section" 380 | version = "1.2.0" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" 383 | 384 | [[package]] 385 | name = "crossbeam-channel" 386 | version = "0.5.15" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" 389 | dependencies = [ 390 | "crossbeam-utils", 391 | ] 392 | 393 | [[package]] 394 | name = "crossbeam-epoch" 395 | version = "0.9.18" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 398 | dependencies = [ 399 | "crossbeam-utils", 400 | ] 401 | 402 | [[package]] 403 | name = "crossbeam-utils" 404 | version = "0.8.21" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 407 | 408 | [[package]] 409 | name = "crypto-common" 410 | version = "0.1.7" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" 413 | dependencies = [ 414 | "generic-array", 415 | "typenum", 416 | ] 417 | 418 | [[package]] 419 | name = "cstr-argument" 420 | version = "0.1.2" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "b6bd9c8e659a473bce955ae5c35b116af38af11a7acb0b480e01f3ed348aeb40" 423 | dependencies = [ 424 | "cfg-if", 425 | "memchr", 426 | ] 427 | 428 | [[package]] 429 | name = "data-encoding" 430 | version = "2.9.0" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" 433 | 434 | [[package]] 435 | name = "deunicode" 436 | version = "1.6.2" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" 439 | 440 | [[package]] 441 | name = "digest" 442 | version = "0.10.7" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 445 | dependencies = [ 446 | "block-buffer", 447 | "crypto-common", 448 | "subtle", 449 | ] 450 | 451 | [[package]] 452 | name = "displaydoc" 453 | version = "0.2.5" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 456 | dependencies = [ 457 | "proc-macro2", 458 | "quote", 459 | "syn", 460 | ] 461 | 462 | [[package]] 463 | name = "dunce" 464 | version = "1.0.5" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" 467 | 468 | [[package]] 469 | name = "either" 470 | version = "1.15.0" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 473 | 474 | [[package]] 475 | name = "encoding_rs" 476 | version = "0.8.35" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 479 | dependencies = [ 480 | "cfg-if", 481 | ] 482 | 483 | [[package]] 484 | name = "endian-type" 485 | version = "0.1.2" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" 488 | 489 | [[package]] 490 | name = "enum-as-inner" 491 | version = "0.6.1" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" 494 | dependencies = [ 495 | "heck", 496 | "proc-macro2", 497 | "quote", 498 | "syn", 499 | ] 500 | 501 | [[package]] 502 | name = "equivalent" 503 | version = "1.0.2" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 506 | 507 | [[package]] 508 | name = "errno" 509 | version = "0.3.14" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" 512 | dependencies = [ 513 | "libc", 514 | "windows-sys 0.61.2", 515 | ] 516 | 517 | [[package]] 518 | name = "eventsource-stream" 519 | version = "0.2.3" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" 522 | dependencies = [ 523 | "futures-core", 524 | "nom", 525 | "pin-project-lite", 526 | ] 527 | 528 | [[package]] 529 | name = "fallible-iterator" 530 | version = "0.2.0" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 533 | 534 | [[package]] 535 | name = "fastrand" 536 | version = "2.3.0" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 539 | 540 | [[package]] 541 | name = "find-msvc-tools" 542 | version = "0.1.5" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" 545 | 546 | [[package]] 547 | name = "fnv" 548 | version = "1.0.7" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 551 | 552 | [[package]] 553 | name = "foldhash" 554 | version = "0.1.5" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 557 | 558 | [[package]] 559 | name = "foldhash" 560 | version = "0.2.0" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" 563 | 564 | [[package]] 565 | name = "foreign-types" 566 | version = "0.3.2" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 569 | dependencies = [ 570 | "foreign-types-shared 0.1.1", 571 | ] 572 | 573 | [[package]] 574 | name = "foreign-types" 575 | version = "0.5.0" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" 578 | dependencies = [ 579 | "foreign-types-macros", 580 | "foreign-types-shared 0.3.1", 581 | ] 582 | 583 | [[package]] 584 | name = "foreign-types-macros" 585 | version = "0.2.3" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" 588 | dependencies = [ 589 | "proc-macro2", 590 | "quote", 591 | "syn", 592 | ] 593 | 594 | [[package]] 595 | name = "foreign-types-shared" 596 | version = "0.1.1" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 599 | 600 | [[package]] 601 | name = "foreign-types-shared" 602 | version = "0.3.1" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" 605 | 606 | [[package]] 607 | name = "form_urlencoded" 608 | version = "1.2.2" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 611 | dependencies = [ 612 | "percent-encoding", 613 | ] 614 | 615 | [[package]] 616 | name = "fs_extra" 617 | version = "1.3.0" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" 620 | 621 | [[package]] 622 | name = "futures" 623 | version = "0.3.31" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 626 | dependencies = [ 627 | "futures-channel", 628 | "futures-core", 629 | "futures-executor", 630 | "futures-io", 631 | "futures-sink", 632 | "futures-task", 633 | "futures-util", 634 | ] 635 | 636 | [[package]] 637 | name = "futures-channel" 638 | version = "0.3.31" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 641 | dependencies = [ 642 | "futures-core", 643 | "futures-sink", 644 | ] 645 | 646 | [[package]] 647 | name = "futures-core" 648 | version = "0.3.31" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 651 | 652 | [[package]] 653 | name = "futures-executor" 654 | version = "0.3.31" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 657 | dependencies = [ 658 | "futures-core", 659 | "futures-task", 660 | "futures-util", 661 | ] 662 | 663 | [[package]] 664 | name = "futures-io" 665 | version = "0.3.31" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 668 | 669 | [[package]] 670 | name = "futures-macro" 671 | version = "0.3.31" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 674 | dependencies = [ 675 | "proc-macro2", 676 | "quote", 677 | "syn", 678 | ] 679 | 680 | [[package]] 681 | name = "futures-sink" 682 | version = "0.3.31" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 685 | 686 | [[package]] 687 | name = "futures-task" 688 | version = "0.3.31" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 691 | 692 | [[package]] 693 | name = "futures-util" 694 | version = "0.3.31" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 697 | dependencies = [ 698 | "futures-channel", 699 | "futures-core", 700 | "futures-io", 701 | "futures-macro", 702 | "futures-sink", 703 | "futures-task", 704 | "memchr", 705 | "pin-project-lite", 706 | "pin-utils", 707 | "slab", 708 | ] 709 | 710 | [[package]] 711 | name = "generic-array" 712 | version = "0.14.7" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 715 | dependencies = [ 716 | "typenum", 717 | "version_check", 718 | ] 719 | 720 | [[package]] 721 | name = "getrandom" 722 | version = "0.2.16" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 725 | dependencies = [ 726 | "cfg-if", 727 | "js-sys", 728 | "libc", 729 | "wasi", 730 | "wasm-bindgen", 731 | ] 732 | 733 | [[package]] 734 | name = "getrandom" 735 | version = "0.3.4" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" 738 | dependencies = [ 739 | "cfg-if", 740 | "js-sys", 741 | "libc", 742 | "r-efi", 743 | "wasip2", 744 | "wasm-bindgen", 745 | ] 746 | 747 | [[package]] 748 | name = "glob" 749 | version = "0.3.3" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" 752 | 753 | [[package]] 754 | name = "h2" 755 | version = "0.4.12" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" 758 | dependencies = [ 759 | "atomic-waker", 760 | "bytes", 761 | "fnv", 762 | "futures-core", 763 | "futures-sink", 764 | "http", 765 | "indexmap", 766 | "slab", 767 | "tokio", 768 | "tokio-util", 769 | "tracing", 770 | ] 771 | 772 | [[package]] 773 | name = "hashbrown" 774 | version = "0.15.5" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 777 | dependencies = [ 778 | "foldhash 0.1.5", 779 | ] 780 | 781 | [[package]] 782 | name = "hashbrown" 783 | version = "0.16.0" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" 786 | dependencies = [ 787 | "allocator-api2", 788 | "equivalent", 789 | "foldhash 0.2.0", 790 | ] 791 | 792 | [[package]] 793 | name = "heck" 794 | version = "0.5.0" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 797 | 798 | [[package]] 799 | name = "hickory-proto" 800 | version = "0.25.2" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" 803 | dependencies = [ 804 | "async-trait", 805 | "cfg-if", 806 | "data-encoding", 807 | "enum-as-inner", 808 | "futures-channel", 809 | "futures-io", 810 | "futures-util", 811 | "idna", 812 | "ipnet", 813 | "once_cell", 814 | "rand", 815 | "ring", 816 | "thiserror 2.0.17", 817 | "tinyvec", 818 | "tokio", 819 | "tracing", 820 | "url", 821 | ] 822 | 823 | [[package]] 824 | name = "hickory-resolver" 825 | version = "0.25.2" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" 828 | dependencies = [ 829 | "cfg-if", 830 | "futures-util", 831 | "hickory-proto", 832 | "ipconfig", 833 | "moka", 834 | "once_cell", 835 | "parking_lot", 836 | "rand", 837 | "resolv-conf", 838 | "smallvec", 839 | "thiserror 2.0.17", 840 | "tokio", 841 | "tracing", 842 | ] 843 | 844 | [[package]] 845 | name = "hmac" 846 | version = "0.12.1" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 849 | dependencies = [ 850 | "digest", 851 | ] 852 | 853 | [[package]] 854 | name = "http" 855 | version = "1.3.1" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 858 | dependencies = [ 859 | "bytes", 860 | "fnv", 861 | "itoa", 862 | ] 863 | 864 | [[package]] 865 | name = "http-body" 866 | version = "1.0.1" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 869 | dependencies = [ 870 | "bytes", 871 | "http", 872 | ] 873 | 874 | [[package]] 875 | name = "http-body-util" 876 | version = "0.1.3" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 879 | dependencies = [ 880 | "bytes", 881 | "futures-core", 882 | "http", 883 | "http-body", 884 | "pin-project-lite", 885 | ] 886 | 887 | [[package]] 888 | name = "http-range-header" 889 | version = "0.4.2" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" 892 | 893 | [[package]] 894 | name = "http_digest_headers" 895 | version = "0.1.0" 896 | source = "registry+https://github.com/rust-lang/crates.io-index" 897 | checksum = "7db39af769c6cb000b7ea765bd5eedcc812d3ec00f3ea50e073c19e00a75916d" 898 | dependencies = [ 899 | "anyhow", 900 | "base64 0.13.1", 901 | "openssl", 902 | "thiserror 1.0.69", 903 | ] 904 | 905 | [[package]] 906 | name = "httparse" 907 | version = "1.10.1" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 910 | 911 | [[package]] 912 | name = "httpdate" 913 | version = "1.0.3" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 916 | 917 | [[package]] 918 | name = "hyper" 919 | version = "1.8.1" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" 922 | dependencies = [ 923 | "atomic-waker", 924 | "bytes", 925 | "futures-channel", 926 | "futures-core", 927 | "h2", 928 | "http", 929 | "http-body", 930 | "httparse", 931 | "httpdate", 932 | "itoa", 933 | "pin-project-lite", 934 | "pin-utils", 935 | "smallvec", 936 | "tokio", 937 | "want", 938 | ] 939 | 940 | [[package]] 941 | name = "hyper-rustls" 942 | version = "0.27.7" 943 | source = "registry+https://github.com/rust-lang/crates.io-index" 944 | checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" 945 | dependencies = [ 946 | "http", 947 | "hyper", 948 | "hyper-util", 949 | "rustls", 950 | "rustls-native-certs", 951 | "rustls-pki-types", 952 | "tokio", 953 | "tokio-rustls", 954 | "tower-service", 955 | "webpki-roots", 956 | ] 957 | 958 | [[package]] 959 | name = "hyper-tls" 960 | version = "0.6.0" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 963 | dependencies = [ 964 | "bytes", 965 | "http-body-util", 966 | "hyper", 967 | "hyper-util", 968 | "native-tls", 969 | "tokio", 970 | "tokio-native-tls", 971 | "tower-service", 972 | ] 973 | 974 | [[package]] 975 | name = "hyper-util" 976 | version = "0.1.18" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" 979 | dependencies = [ 980 | "base64 0.22.1", 981 | "bytes", 982 | "futures-channel", 983 | "futures-core", 984 | "futures-util", 985 | "http", 986 | "http-body", 987 | "hyper", 988 | "ipnet", 989 | "libc", 990 | "percent-encoding", 991 | "pin-project-lite", 992 | "socket2 0.6.1", 993 | "system-configuration", 994 | "tokio", 995 | "tower-service", 996 | "tracing", 997 | "windows-registry", 998 | ] 999 | 1000 | [[package]] 1001 | name = "iana-time-zone" 1002 | version = "0.1.64" 1003 | source = "registry+https://github.com/rust-lang/crates.io-index" 1004 | checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" 1005 | dependencies = [ 1006 | "android_system_properties", 1007 | "core-foundation-sys", 1008 | "iana-time-zone-haiku", 1009 | "js-sys", 1010 | "log", 1011 | "wasm-bindgen", 1012 | "windows-core", 1013 | ] 1014 | 1015 | [[package]] 1016 | name = "iana-time-zone-haiku" 1017 | version = "0.1.2" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 1020 | dependencies = [ 1021 | "cc", 1022 | ] 1023 | 1024 | [[package]] 1025 | name = "icu_collections" 1026 | version = "2.1.1" 1027 | source = "registry+https://github.com/rust-lang/crates.io-index" 1028 | checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" 1029 | dependencies = [ 1030 | "displaydoc", 1031 | "potential_utf", 1032 | "yoke", 1033 | "zerofrom", 1034 | "zerovec", 1035 | ] 1036 | 1037 | [[package]] 1038 | name = "icu_locale_core" 1039 | version = "2.1.1" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" 1042 | dependencies = [ 1043 | "displaydoc", 1044 | "litemap", 1045 | "tinystr", 1046 | "writeable", 1047 | "zerovec", 1048 | ] 1049 | 1050 | [[package]] 1051 | name = "icu_normalizer" 1052 | version = "2.1.1" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" 1055 | dependencies = [ 1056 | "icu_collections", 1057 | "icu_normalizer_data", 1058 | "icu_properties", 1059 | "icu_provider", 1060 | "smallvec", 1061 | "zerovec", 1062 | ] 1063 | 1064 | [[package]] 1065 | name = "icu_normalizer_data" 1066 | version = "2.1.1" 1067 | source = "registry+https://github.com/rust-lang/crates.io-index" 1068 | checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" 1069 | 1070 | [[package]] 1071 | name = "icu_properties" 1072 | version = "2.1.1" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" 1075 | dependencies = [ 1076 | "icu_collections", 1077 | "icu_locale_core", 1078 | "icu_properties_data", 1079 | "icu_provider", 1080 | "zerotrie", 1081 | "zerovec", 1082 | ] 1083 | 1084 | [[package]] 1085 | name = "icu_properties_data" 1086 | version = "2.1.1" 1087 | source = "registry+https://github.com/rust-lang/crates.io-index" 1088 | checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" 1089 | 1090 | [[package]] 1091 | name = "icu_provider" 1092 | version = "2.1.1" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" 1095 | dependencies = [ 1096 | "displaydoc", 1097 | "icu_locale_core", 1098 | "writeable", 1099 | "yoke", 1100 | "zerofrom", 1101 | "zerotrie", 1102 | "zerovec", 1103 | ] 1104 | 1105 | [[package]] 1106 | name = "idna" 1107 | version = "1.1.0" 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" 1109 | checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" 1110 | dependencies = [ 1111 | "idna_adapter", 1112 | "smallvec", 1113 | "utf8_iter", 1114 | ] 1115 | 1116 | [[package]] 1117 | name = "idna_adapter" 1118 | version = "1.2.1" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 1121 | dependencies = [ 1122 | "icu_normalizer", 1123 | "icu_properties", 1124 | ] 1125 | 1126 | [[package]] 1127 | name = "indexmap" 1128 | version = "2.12.0" 1129 | source = "registry+https://github.com/rust-lang/crates.io-index" 1130 | checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" 1131 | dependencies = [ 1132 | "equivalent", 1133 | "hashbrown 0.16.0", 1134 | ] 1135 | 1136 | [[package]] 1137 | name = "ipconfig" 1138 | version = "0.3.2" 1139 | source = "registry+https://github.com/rust-lang/crates.io-index" 1140 | checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" 1141 | dependencies = [ 1142 | "socket2 0.5.10", 1143 | "widestring", 1144 | "windows-sys 0.48.0", 1145 | "winreg", 1146 | ] 1147 | 1148 | [[package]] 1149 | name = "ipnet" 1150 | version = "2.11.0" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 1153 | 1154 | [[package]] 1155 | name = "iri-string" 1156 | version = "0.7.9" 1157 | source = "registry+https://github.com/rust-lang/crates.io-index" 1158 | checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" 1159 | dependencies = [ 1160 | "memchr", 1161 | "serde", 1162 | ] 1163 | 1164 | [[package]] 1165 | name = "itertools" 1166 | version = "0.13.0" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 1169 | dependencies = [ 1170 | "either", 1171 | ] 1172 | 1173 | [[package]] 1174 | name = "itoa" 1175 | version = "1.0.15" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 1178 | 1179 | [[package]] 1180 | name = "jobserver" 1181 | version = "0.1.34" 1182 | source = "registry+https://github.com/rust-lang/crates.io-index" 1183 | checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" 1184 | dependencies = [ 1185 | "getrandom 0.3.4", 1186 | "libc", 1187 | ] 1188 | 1189 | [[package]] 1190 | name = "js-sys" 1191 | version = "0.3.82" 1192 | source = "registry+https://github.com/rust-lang/crates.io-index" 1193 | checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" 1194 | dependencies = [ 1195 | "once_cell", 1196 | "wasm-bindgen", 1197 | ] 1198 | 1199 | [[package]] 1200 | name = "lazy_static" 1201 | version = "1.5.0" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1204 | 1205 | [[package]] 1206 | name = "libc" 1207 | version = "0.2.177" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" 1210 | 1211 | [[package]] 1212 | name = "libloading" 1213 | version = "0.8.9" 1214 | source = "registry+https://github.com/rust-lang/crates.io-index" 1215 | checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" 1216 | dependencies = [ 1217 | "cfg-if", 1218 | "windows-link", 1219 | ] 1220 | 1221 | [[package]] 1222 | name = "libredox" 1223 | version = "0.1.10" 1224 | source = "registry+https://github.com/rust-lang/crates.io-index" 1225 | checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" 1226 | dependencies = [ 1227 | "bitflags", 1228 | "libc", 1229 | "redox_syscall", 1230 | ] 1231 | 1232 | [[package]] 1233 | name = "libsystemd-sys" 1234 | version = "0.9.4" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "976306de183e6046819ef6505888d00996214766a3f4660a2ed5761c84a20aed" 1237 | dependencies = [ 1238 | "build-env", 1239 | "cfg-if", 1240 | "libc", 1241 | "pkg-config", 1242 | ] 1243 | 1244 | [[package]] 1245 | name = "linux-raw-sys" 1246 | version = "0.11.0" 1247 | source = "registry+https://github.com/rust-lang/crates.io-index" 1248 | checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" 1249 | 1250 | [[package]] 1251 | name = "litemap" 1252 | version = "0.8.1" 1253 | source = "registry+https://github.com/rust-lang/crates.io-index" 1254 | checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" 1255 | 1256 | [[package]] 1257 | name = "lock_api" 1258 | version = "0.4.14" 1259 | source = "registry+https://github.com/rust-lang/crates.io-index" 1260 | checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" 1261 | dependencies = [ 1262 | "scopeguard", 1263 | ] 1264 | 1265 | [[package]] 1266 | name = "log" 1267 | version = "0.4.28" 1268 | source = "registry+https://github.com/rust-lang/crates.io-index" 1269 | checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" 1270 | 1271 | [[package]] 1272 | name = "lru" 1273 | version = "0.16.2" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f" 1276 | dependencies = [ 1277 | "hashbrown 0.16.0", 1278 | ] 1279 | 1280 | [[package]] 1281 | name = "lru-slab" 1282 | version = "0.1.2" 1283 | source = "registry+https://github.com/rust-lang/crates.io-index" 1284 | checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" 1285 | 1286 | [[package]] 1287 | name = "matchers" 1288 | version = "0.2.0" 1289 | source = "registry+https://github.com/rust-lang/crates.io-index" 1290 | checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" 1291 | dependencies = [ 1292 | "regex-automata", 1293 | ] 1294 | 1295 | [[package]] 1296 | name = "matchit" 1297 | version = "0.8.4" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" 1300 | 1301 | [[package]] 1302 | name = "md-5" 1303 | version = "0.10.6" 1304 | source = "registry+https://github.com/rust-lang/crates.io-index" 1305 | checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" 1306 | dependencies = [ 1307 | "cfg-if", 1308 | "digest", 1309 | ] 1310 | 1311 | [[package]] 1312 | name = "memchr" 1313 | version = "2.7.6" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 1316 | 1317 | [[package]] 1318 | name = "metrics" 1319 | version = "0.24.2" 1320 | source = "registry+https://github.com/rust-lang/crates.io-index" 1321 | checksum = "25dea7ac8057892855ec285c440160265225438c3c45072613c25a4b26e98ef5" 1322 | dependencies = [ 1323 | "ahash", 1324 | "portable-atomic", 1325 | ] 1326 | 1327 | [[package]] 1328 | name = "metrics-exporter-prometheus" 1329 | version = "0.17.2" 1330 | source = "registry+https://github.com/rust-lang/crates.io-index" 1331 | checksum = "2b166dea96003ee2531cf14833efedced545751d800f03535801d833313f8c15" 1332 | dependencies = [ 1333 | "base64 0.22.1", 1334 | "http-body-util", 1335 | "hyper", 1336 | "hyper-rustls", 1337 | "hyper-util", 1338 | "indexmap", 1339 | "ipnet", 1340 | "metrics", 1341 | "metrics-util", 1342 | "quanta", 1343 | "thiserror 2.0.17", 1344 | "tokio", 1345 | "tracing", 1346 | ] 1347 | 1348 | [[package]] 1349 | name = "metrics-util" 1350 | version = "0.20.0" 1351 | source = "registry+https://github.com/rust-lang/crates.io-index" 1352 | checksum = "fe8db7a05415d0f919ffb905afa37784f71901c9a773188876984b4f769ab986" 1353 | dependencies = [ 1354 | "aho-corasick", 1355 | "crossbeam-epoch", 1356 | "crossbeam-utils", 1357 | "hashbrown 0.15.5", 1358 | "indexmap", 1359 | "metrics", 1360 | "ordered-float", 1361 | "quanta", 1362 | "radix_trie", 1363 | "rand", 1364 | "rand_xoshiro", 1365 | "sketches-ddsketch", 1366 | ] 1367 | 1368 | [[package]] 1369 | name = "mime" 1370 | version = "0.3.17" 1371 | source = "registry+https://github.com/rust-lang/crates.io-index" 1372 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1373 | 1374 | [[package]] 1375 | name = "mime_guess" 1376 | version = "2.0.5" 1377 | source = "registry+https://github.com/rust-lang/crates.io-index" 1378 | checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" 1379 | dependencies = [ 1380 | "mime", 1381 | "unicase", 1382 | ] 1383 | 1384 | [[package]] 1385 | name = "minimal-lexical" 1386 | version = "0.2.1" 1387 | source = "registry+https://github.com/rust-lang/crates.io-index" 1388 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 1389 | 1390 | [[package]] 1391 | name = "mio" 1392 | version = "1.1.0" 1393 | source = "registry+https://github.com/rust-lang/crates.io-index" 1394 | checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" 1395 | dependencies = [ 1396 | "libc", 1397 | "wasi", 1398 | "windows-sys 0.61.2", 1399 | ] 1400 | 1401 | [[package]] 1402 | name = "moka" 1403 | version = "0.12.11" 1404 | source = "registry+https://github.com/rust-lang/crates.io-index" 1405 | checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" 1406 | dependencies = [ 1407 | "crossbeam-channel", 1408 | "crossbeam-epoch", 1409 | "crossbeam-utils", 1410 | "equivalent", 1411 | "parking_lot", 1412 | "portable-atomic", 1413 | "rustc_version", 1414 | "smallvec", 1415 | "tagptr", 1416 | "uuid", 1417 | ] 1418 | 1419 | [[package]] 1420 | name = "native-tls" 1421 | version = "0.2.14" 1422 | source = "registry+https://github.com/rust-lang/crates.io-index" 1423 | checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" 1424 | dependencies = [ 1425 | "libc", 1426 | "log", 1427 | "openssl", 1428 | "openssl-probe", 1429 | "openssl-sys", 1430 | "schannel", 1431 | "security-framework 2.11.1", 1432 | "security-framework-sys", 1433 | "tempfile", 1434 | ] 1435 | 1436 | [[package]] 1437 | name = "nibble_vec" 1438 | version = "0.1.0" 1439 | source = "registry+https://github.com/rust-lang/crates.io-index" 1440 | checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" 1441 | dependencies = [ 1442 | "smallvec", 1443 | ] 1444 | 1445 | [[package]] 1446 | name = "nom" 1447 | version = "7.1.3" 1448 | source = "registry+https://github.com/rust-lang/crates.io-index" 1449 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 1450 | dependencies = [ 1451 | "memchr", 1452 | "minimal-lexical", 1453 | ] 1454 | 1455 | [[package]] 1456 | name = "nu-ansi-term" 1457 | version = "0.50.3" 1458 | source = "registry+https://github.com/rust-lang/crates.io-index" 1459 | checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" 1460 | dependencies = [ 1461 | "windows-sys 0.61.2", 1462 | ] 1463 | 1464 | [[package]] 1465 | name = "num-bigint" 1466 | version = "0.4.6" 1467 | source = "registry+https://github.com/rust-lang/crates.io-index" 1468 | checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" 1469 | dependencies = [ 1470 | "num-integer", 1471 | "num-traits", 1472 | ] 1473 | 1474 | [[package]] 1475 | name = "num-integer" 1476 | version = "0.1.46" 1477 | source = "registry+https://github.com/rust-lang/crates.io-index" 1478 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 1479 | dependencies = [ 1480 | "num-traits", 1481 | ] 1482 | 1483 | [[package]] 1484 | name = "num-traits" 1485 | version = "0.2.19" 1486 | source = "registry+https://github.com/rust-lang/crates.io-index" 1487 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1488 | dependencies = [ 1489 | "autocfg", 1490 | ] 1491 | 1492 | [[package]] 1493 | name = "once_cell" 1494 | version = "1.21.3" 1495 | source = "registry+https://github.com/rust-lang/crates.io-index" 1496 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 1497 | dependencies = [ 1498 | "critical-section", 1499 | "portable-atomic", 1500 | ] 1501 | 1502 | [[package]] 1503 | name = "openssl" 1504 | version = "0.10.75" 1505 | source = "registry+https://github.com/rust-lang/crates.io-index" 1506 | checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" 1507 | dependencies = [ 1508 | "bitflags", 1509 | "cfg-if", 1510 | "foreign-types 0.3.2", 1511 | "libc", 1512 | "once_cell", 1513 | "openssl-macros", 1514 | "openssl-sys", 1515 | ] 1516 | 1517 | [[package]] 1518 | name = "openssl-macros" 1519 | version = "0.1.1" 1520 | source = "registry+https://github.com/rust-lang/crates.io-index" 1521 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1522 | dependencies = [ 1523 | "proc-macro2", 1524 | "quote", 1525 | "syn", 1526 | ] 1527 | 1528 | [[package]] 1529 | name = "openssl-probe" 1530 | version = "0.1.6" 1531 | source = "registry+https://github.com/rust-lang/crates.io-index" 1532 | checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 1533 | 1534 | [[package]] 1535 | name = "openssl-sys" 1536 | version = "0.9.111" 1537 | source = "registry+https://github.com/rust-lang/crates.io-index" 1538 | checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" 1539 | dependencies = [ 1540 | "cc", 1541 | "libc", 1542 | "pkg-config", 1543 | "vcpkg", 1544 | ] 1545 | 1546 | [[package]] 1547 | name = "ordered-float" 1548 | version = "4.6.0" 1549 | source = "registry+https://github.com/rust-lang/crates.io-index" 1550 | checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" 1551 | dependencies = [ 1552 | "num-traits", 1553 | ] 1554 | 1555 | [[package]] 1556 | name = "parking_lot" 1557 | version = "0.12.5" 1558 | source = "registry+https://github.com/rust-lang/crates.io-index" 1559 | checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" 1560 | dependencies = [ 1561 | "lock_api", 1562 | "parking_lot_core", 1563 | ] 1564 | 1565 | [[package]] 1566 | name = "parking_lot_core" 1567 | version = "0.9.12" 1568 | source = "registry+https://github.com/rust-lang/crates.io-index" 1569 | checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" 1570 | dependencies = [ 1571 | "cfg-if", 1572 | "libc", 1573 | "redox_syscall", 1574 | "smallvec", 1575 | "windows-link", 1576 | ] 1577 | 1578 | [[package]] 1579 | name = "percent-encoding" 1580 | version = "2.3.2" 1581 | source = "registry+https://github.com/rust-lang/crates.io-index" 1582 | checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 1583 | 1584 | [[package]] 1585 | name = "phf" 1586 | version = "0.13.1" 1587 | source = "registry+https://github.com/rust-lang/crates.io-index" 1588 | checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" 1589 | dependencies = [ 1590 | "phf_shared", 1591 | "serde", 1592 | ] 1593 | 1594 | [[package]] 1595 | name = "phf_shared" 1596 | version = "0.13.1" 1597 | source = "registry+https://github.com/rust-lang/crates.io-index" 1598 | checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" 1599 | dependencies = [ 1600 | "siphasher", 1601 | ] 1602 | 1603 | [[package]] 1604 | name = "pin-project-lite" 1605 | version = "0.2.16" 1606 | source = "registry+https://github.com/rust-lang/crates.io-index" 1607 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1608 | 1609 | [[package]] 1610 | name = "pin-utils" 1611 | version = "0.1.0" 1612 | source = "registry+https://github.com/rust-lang/crates.io-index" 1613 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1614 | 1615 | [[package]] 1616 | name = "pkg-config" 1617 | version = "0.3.32" 1618 | source = "registry+https://github.com/rust-lang/crates.io-index" 1619 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 1620 | 1621 | [[package]] 1622 | name = "portable-atomic" 1623 | version = "1.11.1" 1624 | source = "registry+https://github.com/rust-lang/crates.io-index" 1625 | checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" 1626 | 1627 | [[package]] 1628 | name = "postgres-protocol" 1629 | version = "0.6.9" 1630 | source = "registry+https://github.com/rust-lang/crates.io-index" 1631 | checksum = "fbef655056b916eb868048276cfd5d6a7dea4f81560dfd047f97c8c6fe3fcfd4" 1632 | dependencies = [ 1633 | "base64 0.22.1", 1634 | "byteorder", 1635 | "bytes", 1636 | "fallible-iterator", 1637 | "hmac", 1638 | "md-5", 1639 | "memchr", 1640 | "rand", 1641 | "sha2", 1642 | "stringprep", 1643 | ] 1644 | 1645 | [[package]] 1646 | name = "postgres-types" 1647 | version = "0.2.11" 1648 | source = "registry+https://github.com/rust-lang/crates.io-index" 1649 | checksum = "ef4605b7c057056dd35baeb6ac0c0338e4975b1f2bef0f65da953285eb007095" 1650 | dependencies = [ 1651 | "bytes", 1652 | "fallible-iterator", 1653 | "postgres-protocol", 1654 | ] 1655 | 1656 | [[package]] 1657 | name = "potential_utf" 1658 | version = "0.1.4" 1659 | source = "registry+https://github.com/rust-lang/crates.io-index" 1660 | checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" 1661 | dependencies = [ 1662 | "zerovec", 1663 | ] 1664 | 1665 | [[package]] 1666 | name = "ppv-lite86" 1667 | version = "0.2.21" 1668 | source = "registry+https://github.com/rust-lang/crates.io-index" 1669 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 1670 | dependencies = [ 1671 | "zerocopy", 1672 | ] 1673 | 1674 | [[package]] 1675 | name = "prettyplease" 1676 | version = "0.2.37" 1677 | source = "registry+https://github.com/rust-lang/crates.io-index" 1678 | checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" 1679 | dependencies = [ 1680 | "proc-macro2", 1681 | "syn", 1682 | ] 1683 | 1684 | [[package]] 1685 | name = "proc-macro2" 1686 | version = "1.0.103" 1687 | source = "registry+https://github.com/rust-lang/crates.io-index" 1688 | checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" 1689 | dependencies = [ 1690 | "unicode-ident", 1691 | ] 1692 | 1693 | [[package]] 1694 | name = "quanta" 1695 | version = "0.12.6" 1696 | source = "registry+https://github.com/rust-lang/crates.io-index" 1697 | checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" 1698 | dependencies = [ 1699 | "crossbeam-utils", 1700 | "libc", 1701 | "once_cell", 1702 | "raw-cpuid", 1703 | "wasi", 1704 | "web-sys", 1705 | "winapi", 1706 | ] 1707 | 1708 | [[package]] 1709 | name = "quinn" 1710 | version = "0.11.9" 1711 | source = "registry+https://github.com/rust-lang/crates.io-index" 1712 | checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" 1713 | dependencies = [ 1714 | "bytes", 1715 | "cfg_aliases", 1716 | "pin-project-lite", 1717 | "quinn-proto", 1718 | "quinn-udp", 1719 | "rustc-hash", 1720 | "rustls", 1721 | "socket2 0.6.1", 1722 | "thiserror 2.0.17", 1723 | "tokio", 1724 | "tracing", 1725 | "web-time", 1726 | ] 1727 | 1728 | [[package]] 1729 | name = "quinn-proto" 1730 | version = "0.11.13" 1731 | source = "registry+https://github.com/rust-lang/crates.io-index" 1732 | checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" 1733 | dependencies = [ 1734 | "bytes", 1735 | "getrandom 0.3.4", 1736 | "lru-slab", 1737 | "rand", 1738 | "ring", 1739 | "rustc-hash", 1740 | "rustls", 1741 | "rustls-pki-types", 1742 | "slab", 1743 | "thiserror 2.0.17", 1744 | "tinyvec", 1745 | "tracing", 1746 | "web-time", 1747 | ] 1748 | 1749 | [[package]] 1750 | name = "quinn-udp" 1751 | version = "0.5.14" 1752 | source = "registry+https://github.com/rust-lang/crates.io-index" 1753 | checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" 1754 | dependencies = [ 1755 | "cfg_aliases", 1756 | "libc", 1757 | "once_cell", 1758 | "socket2 0.6.1", 1759 | "tracing", 1760 | "windows-sys 0.60.2", 1761 | ] 1762 | 1763 | [[package]] 1764 | name = "quote" 1765 | version = "1.0.42" 1766 | source = "registry+https://github.com/rust-lang/crates.io-index" 1767 | checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" 1768 | dependencies = [ 1769 | "proc-macro2", 1770 | ] 1771 | 1772 | [[package]] 1773 | name = "r-efi" 1774 | version = "5.3.0" 1775 | source = "registry+https://github.com/rust-lang/crates.io-index" 1776 | checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 1777 | 1778 | [[package]] 1779 | name = "radix_trie" 1780 | version = "0.2.1" 1781 | source = "registry+https://github.com/rust-lang/crates.io-index" 1782 | checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" 1783 | dependencies = [ 1784 | "endian-type", 1785 | "nibble_vec", 1786 | ] 1787 | 1788 | [[package]] 1789 | name = "rand" 1790 | version = "0.9.2" 1791 | source = "registry+https://github.com/rust-lang/crates.io-index" 1792 | checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" 1793 | dependencies = [ 1794 | "rand_chacha", 1795 | "rand_core", 1796 | ] 1797 | 1798 | [[package]] 1799 | name = "rand_chacha" 1800 | version = "0.9.0" 1801 | source = "registry+https://github.com/rust-lang/crates.io-index" 1802 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 1803 | dependencies = [ 1804 | "ppv-lite86", 1805 | "rand_core", 1806 | ] 1807 | 1808 | [[package]] 1809 | name = "rand_core" 1810 | version = "0.9.3" 1811 | source = "registry+https://github.com/rust-lang/crates.io-index" 1812 | checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 1813 | dependencies = [ 1814 | "getrandom 0.3.4", 1815 | ] 1816 | 1817 | [[package]] 1818 | name = "rand_xoshiro" 1819 | version = "0.7.0" 1820 | source = "registry+https://github.com/rust-lang/crates.io-index" 1821 | checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" 1822 | dependencies = [ 1823 | "rand_core", 1824 | ] 1825 | 1826 | [[package]] 1827 | name = "raw-cpuid" 1828 | version = "11.6.0" 1829 | source = "registry+https://github.com/rust-lang/crates.io-index" 1830 | checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" 1831 | dependencies = [ 1832 | "bitflags", 1833 | ] 1834 | 1835 | [[package]] 1836 | name = "redis" 1837 | version = "0.32.7" 1838 | source = "registry+https://github.com/rust-lang/crates.io-index" 1839 | checksum = "014cc767fefab6a3e798ca45112bccad9c6e0e218fbd49720042716c73cfef44" 1840 | dependencies = [ 1841 | "arc-swap", 1842 | "backon", 1843 | "bytes", 1844 | "cfg-if", 1845 | "combine", 1846 | "futures-channel", 1847 | "futures-util", 1848 | "itoa", 1849 | "num-bigint", 1850 | "percent-encoding", 1851 | "pin-project-lite", 1852 | "ryu", 1853 | "sha1_smol", 1854 | "socket2 0.6.1", 1855 | "tokio", 1856 | "tokio-util", 1857 | "url", 1858 | ] 1859 | 1860 | [[package]] 1861 | name = "redox_syscall" 1862 | version = "0.5.18" 1863 | source = "registry+https://github.com/rust-lang/crates.io-index" 1864 | checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" 1865 | dependencies = [ 1866 | "bitflags", 1867 | ] 1868 | 1869 | [[package]] 1870 | name = "regex" 1871 | version = "1.12.2" 1872 | source = "registry+https://github.com/rust-lang/crates.io-index" 1873 | checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" 1874 | dependencies = [ 1875 | "aho-corasick", 1876 | "memchr", 1877 | "regex-automata", 1878 | "regex-syntax", 1879 | ] 1880 | 1881 | [[package]] 1882 | name = "regex-automata" 1883 | version = "0.4.13" 1884 | source = "registry+https://github.com/rust-lang/crates.io-index" 1885 | checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" 1886 | dependencies = [ 1887 | "aho-corasick", 1888 | "memchr", 1889 | "regex-syntax", 1890 | ] 1891 | 1892 | [[package]] 1893 | name = "regex-syntax" 1894 | version = "0.8.8" 1895 | source = "registry+https://github.com/rust-lang/crates.io-index" 1896 | checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" 1897 | 1898 | [[package]] 1899 | name = "reqwest" 1900 | version = "0.12.24" 1901 | source = "registry+https://github.com/rust-lang/crates.io-index" 1902 | checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" 1903 | dependencies = [ 1904 | "base64 0.22.1", 1905 | "bytes", 1906 | "encoding_rs", 1907 | "futures-core", 1908 | "futures-util", 1909 | "h2", 1910 | "hickory-resolver", 1911 | "http", 1912 | "http-body", 1913 | "http-body-util", 1914 | "hyper", 1915 | "hyper-rustls", 1916 | "hyper-tls", 1917 | "hyper-util", 1918 | "js-sys", 1919 | "log", 1920 | "mime", 1921 | "native-tls", 1922 | "once_cell", 1923 | "percent-encoding", 1924 | "pin-project-lite", 1925 | "quinn", 1926 | "rustls", 1927 | "rustls-pki-types", 1928 | "serde", 1929 | "serde_json", 1930 | "serde_urlencoded", 1931 | "sync_wrapper", 1932 | "tokio", 1933 | "tokio-native-tls", 1934 | "tokio-rustls", 1935 | "tokio-util", 1936 | "tower", 1937 | "tower-http", 1938 | "tower-service", 1939 | "url", 1940 | "wasm-bindgen", 1941 | "wasm-bindgen-futures", 1942 | "wasm-streams", 1943 | "web-sys", 1944 | "webpki-roots", 1945 | ] 1946 | 1947 | [[package]] 1948 | name = "resolv-conf" 1949 | version = "0.7.5" 1950 | source = "registry+https://github.com/rust-lang/crates.io-index" 1951 | checksum = "6b3789b30bd25ba102de4beabd95d21ac45b69b1be7d14522bab988c526d6799" 1952 | 1953 | [[package]] 1954 | name = "ring" 1955 | version = "0.17.14" 1956 | source = "registry+https://github.com/rust-lang/crates.io-index" 1957 | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 1958 | dependencies = [ 1959 | "cc", 1960 | "cfg-if", 1961 | "getrandom 0.2.16", 1962 | "libc", 1963 | "untrusted", 1964 | "windows-sys 0.52.0", 1965 | ] 1966 | 1967 | [[package]] 1968 | name = "rustc-hash" 1969 | version = "2.1.1" 1970 | source = "registry+https://github.com/rust-lang/crates.io-index" 1971 | checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 1972 | 1973 | [[package]] 1974 | name = "rustc_version" 1975 | version = "0.4.1" 1976 | source = "registry+https://github.com/rust-lang/crates.io-index" 1977 | checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 1978 | dependencies = [ 1979 | "semver", 1980 | ] 1981 | 1982 | [[package]] 1983 | name = "rustix" 1984 | version = "1.1.2" 1985 | source = "registry+https://github.com/rust-lang/crates.io-index" 1986 | checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" 1987 | dependencies = [ 1988 | "bitflags", 1989 | "errno", 1990 | "libc", 1991 | "linux-raw-sys", 1992 | "windows-sys 0.61.2", 1993 | ] 1994 | 1995 | [[package]] 1996 | name = "rustls" 1997 | version = "0.23.35" 1998 | source = "registry+https://github.com/rust-lang/crates.io-index" 1999 | checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" 2000 | dependencies = [ 2001 | "aws-lc-rs", 2002 | "once_cell", 2003 | "ring", 2004 | "rustls-pki-types", 2005 | "rustls-webpki", 2006 | "subtle", 2007 | "zeroize", 2008 | ] 2009 | 2010 | [[package]] 2011 | name = "rustls-native-certs" 2012 | version = "0.8.2" 2013 | source = "registry+https://github.com/rust-lang/crates.io-index" 2014 | checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" 2015 | dependencies = [ 2016 | "openssl-probe", 2017 | "rustls-pki-types", 2018 | "schannel", 2019 | "security-framework 3.5.1", 2020 | ] 2021 | 2022 | [[package]] 2023 | name = "rustls-pki-types" 2024 | version = "1.13.0" 2025 | source = "registry+https://github.com/rust-lang/crates.io-index" 2026 | checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" 2027 | dependencies = [ 2028 | "web-time", 2029 | "zeroize", 2030 | ] 2031 | 2032 | [[package]] 2033 | name = "rustls-webpki" 2034 | version = "0.103.8" 2035 | source = "registry+https://github.com/rust-lang/crates.io-index" 2036 | checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" 2037 | dependencies = [ 2038 | "aws-lc-rs", 2039 | "ring", 2040 | "rustls-pki-types", 2041 | "untrusted", 2042 | ] 2043 | 2044 | [[package]] 2045 | name = "rustversion" 2046 | version = "1.0.22" 2047 | source = "registry+https://github.com/rust-lang/crates.io-index" 2048 | checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 2049 | 2050 | [[package]] 2051 | name = "ryu" 2052 | version = "1.0.20" 2053 | source = "registry+https://github.com/rust-lang/crates.io-index" 2054 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 2055 | 2056 | [[package]] 2057 | name = "schannel" 2058 | version = "0.1.28" 2059 | source = "registry+https://github.com/rust-lang/crates.io-index" 2060 | checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" 2061 | dependencies = [ 2062 | "windows-sys 0.61.2", 2063 | ] 2064 | 2065 | [[package]] 2066 | name = "scopeguard" 2067 | version = "1.2.0" 2068 | source = "registry+https://github.com/rust-lang/crates.io-index" 2069 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 2070 | 2071 | [[package]] 2072 | name = "security-framework" 2073 | version = "2.11.1" 2074 | source = "registry+https://github.com/rust-lang/crates.io-index" 2075 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 2076 | dependencies = [ 2077 | "bitflags", 2078 | "core-foundation 0.9.4", 2079 | "core-foundation-sys", 2080 | "libc", 2081 | "security-framework-sys", 2082 | ] 2083 | 2084 | [[package]] 2085 | name = "security-framework" 2086 | version = "3.5.1" 2087 | source = "registry+https://github.com/rust-lang/crates.io-index" 2088 | checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" 2089 | dependencies = [ 2090 | "bitflags", 2091 | "core-foundation 0.10.1", 2092 | "core-foundation-sys", 2093 | "libc", 2094 | "security-framework-sys", 2095 | ] 2096 | 2097 | [[package]] 2098 | name = "security-framework-sys" 2099 | version = "2.15.0" 2100 | source = "registry+https://github.com/rust-lang/crates.io-index" 2101 | checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" 2102 | dependencies = [ 2103 | "core-foundation-sys", 2104 | "libc", 2105 | ] 2106 | 2107 | [[package]] 2108 | name = "semver" 2109 | version = "1.0.27" 2110 | source = "registry+https://github.com/rust-lang/crates.io-index" 2111 | checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" 2112 | 2113 | [[package]] 2114 | name = "serde" 2115 | version = "1.0.228" 2116 | source = "registry+https://github.com/rust-lang/crates.io-index" 2117 | checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 2118 | dependencies = [ 2119 | "serde_core", 2120 | "serde_derive", 2121 | ] 2122 | 2123 | [[package]] 2124 | name = "serde_core" 2125 | version = "1.0.228" 2126 | source = "registry+https://github.com/rust-lang/crates.io-index" 2127 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 2128 | dependencies = [ 2129 | "serde_derive", 2130 | ] 2131 | 2132 | [[package]] 2133 | name = "serde_derive" 2134 | version = "1.0.228" 2135 | source = "registry+https://github.com/rust-lang/crates.io-index" 2136 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 2137 | dependencies = [ 2138 | "proc-macro2", 2139 | "quote", 2140 | "syn", 2141 | ] 2142 | 2143 | [[package]] 2144 | name = "serde_json" 2145 | version = "1.0.145" 2146 | source = "registry+https://github.com/rust-lang/crates.io-index" 2147 | checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 2148 | dependencies = [ 2149 | "itoa", 2150 | "memchr", 2151 | "ryu", 2152 | "serde", 2153 | "serde_core", 2154 | ] 2155 | 2156 | [[package]] 2157 | name = "serde_path_to_error" 2158 | version = "0.1.20" 2159 | source = "registry+https://github.com/rust-lang/crates.io-index" 2160 | checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" 2161 | dependencies = [ 2162 | "itoa", 2163 | "serde", 2164 | "serde_core", 2165 | ] 2166 | 2167 | [[package]] 2168 | name = "serde_urlencoded" 2169 | version = "0.7.1" 2170 | source = "registry+https://github.com/rust-lang/crates.io-index" 2171 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 2172 | dependencies = [ 2173 | "form_urlencoded", 2174 | "itoa", 2175 | "ryu", 2176 | "serde", 2177 | ] 2178 | 2179 | [[package]] 2180 | name = "serde_yaml" 2181 | version = "0.9.34+deprecated" 2182 | source = "registry+https://github.com/rust-lang/crates.io-index" 2183 | checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" 2184 | dependencies = [ 2185 | "indexmap", 2186 | "itoa", 2187 | "ryu", 2188 | "serde", 2189 | "unsafe-libyaml", 2190 | ] 2191 | 2192 | [[package]] 2193 | name = "sha1_smol" 2194 | version = "1.0.1" 2195 | source = "registry+https://github.com/rust-lang/crates.io-index" 2196 | checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" 2197 | 2198 | [[package]] 2199 | name = "sha2" 2200 | version = "0.10.9" 2201 | source = "registry+https://github.com/rust-lang/crates.io-index" 2202 | checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 2203 | dependencies = [ 2204 | "cfg-if", 2205 | "cpufeatures", 2206 | "digest", 2207 | ] 2208 | 2209 | [[package]] 2210 | name = "sharded-slab" 2211 | version = "0.1.7" 2212 | source = "registry+https://github.com/rust-lang/crates.io-index" 2213 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 2214 | dependencies = [ 2215 | "lazy_static", 2216 | ] 2217 | 2218 | [[package]] 2219 | name = "shlex" 2220 | version = "1.3.0" 2221 | source = "registry+https://github.com/rust-lang/crates.io-index" 2222 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 2223 | 2224 | [[package]] 2225 | name = "sigh" 2226 | version = "1.0.3" 2227 | source = "registry+https://github.com/rust-lang/crates.io-index" 2228 | checksum = "459e7512be4c319df13c721de4a1382d770e492fcdbfe9276a6b6888e5f2a2e4" 2229 | dependencies = [ 2230 | "base64 0.22.1", 2231 | "http", 2232 | "nom", 2233 | "openssl", 2234 | "thiserror 2.0.17", 2235 | ] 2236 | 2237 | [[package]] 2238 | name = "signal-hook-registry" 2239 | version = "1.4.6" 2240 | source = "registry+https://github.com/rust-lang/crates.io-index" 2241 | checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" 2242 | dependencies = [ 2243 | "libc", 2244 | ] 2245 | 2246 | [[package]] 2247 | name = "siphasher" 2248 | version = "1.0.1" 2249 | source = "registry+https://github.com/rust-lang/crates.io-index" 2250 | checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" 2251 | 2252 | [[package]] 2253 | name = "sketches-ddsketch" 2254 | version = "0.3.0" 2255 | source = "registry+https://github.com/rust-lang/crates.io-index" 2256 | checksum = "c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a" 2257 | 2258 | [[package]] 2259 | name = "slab" 2260 | version = "0.4.11" 2261 | source = "registry+https://github.com/rust-lang/crates.io-index" 2262 | checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" 2263 | 2264 | [[package]] 2265 | name = "smallvec" 2266 | version = "1.15.1" 2267 | source = "registry+https://github.com/rust-lang/crates.io-index" 2268 | checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 2269 | 2270 | [[package]] 2271 | name = "socket2" 2272 | version = "0.5.10" 2273 | source = "registry+https://github.com/rust-lang/crates.io-index" 2274 | checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" 2275 | dependencies = [ 2276 | "libc", 2277 | "windows-sys 0.52.0", 2278 | ] 2279 | 2280 | [[package]] 2281 | name = "socket2" 2282 | version = "0.6.1" 2283 | source = "registry+https://github.com/rust-lang/crates.io-index" 2284 | checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" 2285 | dependencies = [ 2286 | "libc", 2287 | "windows-sys 0.60.2", 2288 | ] 2289 | 2290 | [[package]] 2291 | name = "stable_deref_trait" 2292 | version = "1.2.1" 2293 | source = "registry+https://github.com/rust-lang/crates.io-index" 2294 | checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" 2295 | 2296 | [[package]] 2297 | name = "stringprep" 2298 | version = "0.1.5" 2299 | source = "registry+https://github.com/rust-lang/crates.io-index" 2300 | checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" 2301 | dependencies = [ 2302 | "unicode-bidi", 2303 | "unicode-normalization", 2304 | "unicode-properties", 2305 | ] 2306 | 2307 | [[package]] 2308 | name = "subtle" 2309 | version = "2.6.1" 2310 | source = "registry+https://github.com/rust-lang/crates.io-index" 2311 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 2312 | 2313 | [[package]] 2314 | name = "syn" 2315 | version = "2.0.110" 2316 | source = "registry+https://github.com/rust-lang/crates.io-index" 2317 | checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" 2318 | dependencies = [ 2319 | "proc-macro2", 2320 | "quote", 2321 | "unicode-ident", 2322 | ] 2323 | 2324 | [[package]] 2325 | name = "sync_wrapper" 2326 | version = "1.0.2" 2327 | source = "registry+https://github.com/rust-lang/crates.io-index" 2328 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 2329 | dependencies = [ 2330 | "futures-core", 2331 | ] 2332 | 2333 | [[package]] 2334 | name = "synstructure" 2335 | version = "0.13.2" 2336 | source = "registry+https://github.com/rust-lang/crates.io-index" 2337 | checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 2338 | dependencies = [ 2339 | "proc-macro2", 2340 | "quote", 2341 | "syn", 2342 | ] 2343 | 2344 | [[package]] 2345 | name = "system-configuration" 2346 | version = "0.6.1" 2347 | source = "registry+https://github.com/rust-lang/crates.io-index" 2348 | checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 2349 | dependencies = [ 2350 | "bitflags", 2351 | "core-foundation 0.9.4", 2352 | "system-configuration-sys", 2353 | ] 2354 | 2355 | [[package]] 2356 | name = "system-configuration-sys" 2357 | version = "0.6.0" 2358 | source = "registry+https://github.com/rust-lang/crates.io-index" 2359 | checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 2360 | dependencies = [ 2361 | "core-foundation-sys", 2362 | "libc", 2363 | ] 2364 | 2365 | [[package]] 2366 | name = "systemd" 2367 | version = "0.10.1" 2368 | source = "registry+https://github.com/rust-lang/crates.io-index" 2369 | checksum = "01e9d1976a15b86245def55d20d52b5818e1a1e81aa030b6a608d3ce57709423" 2370 | dependencies = [ 2371 | "cstr-argument", 2372 | "foreign-types 0.5.0", 2373 | "libc", 2374 | "libsystemd-sys", 2375 | "log", 2376 | "memchr", 2377 | "utf8-cstr", 2378 | ] 2379 | 2380 | [[package]] 2381 | name = "tagptr" 2382 | version = "0.2.0" 2383 | source = "registry+https://github.com/rust-lang/crates.io-index" 2384 | checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" 2385 | 2386 | [[package]] 2387 | name = "tempfile" 2388 | version = "3.23.0" 2389 | source = "registry+https://github.com/rust-lang/crates.io-index" 2390 | checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" 2391 | dependencies = [ 2392 | "fastrand", 2393 | "getrandom 0.3.4", 2394 | "once_cell", 2395 | "rustix", 2396 | "windows-sys 0.61.2", 2397 | ] 2398 | 2399 | [[package]] 2400 | name = "thiserror" 2401 | version = "1.0.69" 2402 | source = "registry+https://github.com/rust-lang/crates.io-index" 2403 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 2404 | dependencies = [ 2405 | "thiserror-impl 1.0.69", 2406 | ] 2407 | 2408 | [[package]] 2409 | name = "thiserror" 2410 | version = "2.0.17" 2411 | source = "registry+https://github.com/rust-lang/crates.io-index" 2412 | checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" 2413 | dependencies = [ 2414 | "thiserror-impl 2.0.17", 2415 | ] 2416 | 2417 | [[package]] 2418 | name = "thiserror-impl" 2419 | version = "1.0.69" 2420 | source = "registry+https://github.com/rust-lang/crates.io-index" 2421 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 2422 | dependencies = [ 2423 | "proc-macro2", 2424 | "quote", 2425 | "syn", 2426 | ] 2427 | 2428 | [[package]] 2429 | name = "thiserror-impl" 2430 | version = "2.0.17" 2431 | source = "registry+https://github.com/rust-lang/crates.io-index" 2432 | checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" 2433 | dependencies = [ 2434 | "proc-macro2", 2435 | "quote", 2436 | "syn", 2437 | ] 2438 | 2439 | [[package]] 2440 | name = "thread_local" 2441 | version = "1.1.9" 2442 | source = "registry+https://github.com/rust-lang/crates.io-index" 2443 | checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" 2444 | dependencies = [ 2445 | "cfg-if", 2446 | ] 2447 | 2448 | [[package]] 2449 | name = "tinystr" 2450 | version = "0.8.2" 2451 | source = "registry+https://github.com/rust-lang/crates.io-index" 2452 | checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" 2453 | dependencies = [ 2454 | "displaydoc", 2455 | "zerovec", 2456 | ] 2457 | 2458 | [[package]] 2459 | name = "tinyvec" 2460 | version = "1.10.0" 2461 | source = "registry+https://github.com/rust-lang/crates.io-index" 2462 | checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" 2463 | dependencies = [ 2464 | "tinyvec_macros", 2465 | ] 2466 | 2467 | [[package]] 2468 | name = "tinyvec_macros" 2469 | version = "0.1.1" 2470 | source = "registry+https://github.com/rust-lang/crates.io-index" 2471 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 2472 | 2473 | [[package]] 2474 | name = "tokio" 2475 | version = "1.48.0" 2476 | source = "registry+https://github.com/rust-lang/crates.io-index" 2477 | checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" 2478 | dependencies = [ 2479 | "bytes", 2480 | "libc", 2481 | "mio", 2482 | "parking_lot", 2483 | "pin-project-lite", 2484 | "signal-hook-registry", 2485 | "socket2 0.6.1", 2486 | "tokio-macros", 2487 | "windows-sys 0.61.2", 2488 | ] 2489 | 2490 | [[package]] 2491 | name = "tokio-macros" 2492 | version = "2.6.0" 2493 | source = "registry+https://github.com/rust-lang/crates.io-index" 2494 | checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" 2495 | dependencies = [ 2496 | "proc-macro2", 2497 | "quote", 2498 | "syn", 2499 | ] 2500 | 2501 | [[package]] 2502 | name = "tokio-native-tls" 2503 | version = "0.3.1" 2504 | source = "registry+https://github.com/rust-lang/crates.io-index" 2505 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 2506 | dependencies = [ 2507 | "native-tls", 2508 | "tokio", 2509 | ] 2510 | 2511 | [[package]] 2512 | name = "tokio-postgres" 2513 | version = "0.7.15" 2514 | source = "registry+https://github.com/rust-lang/crates.io-index" 2515 | checksum = "2b40d66d9b2cfe04b628173409368e58247e8eddbbd3b0e6c6ba1d09f20f6c9e" 2516 | dependencies = [ 2517 | "async-trait", 2518 | "byteorder", 2519 | "bytes", 2520 | "fallible-iterator", 2521 | "futures-channel", 2522 | "futures-util", 2523 | "log", 2524 | "parking_lot", 2525 | "percent-encoding", 2526 | "phf", 2527 | "pin-project-lite", 2528 | "postgres-protocol", 2529 | "postgres-types", 2530 | "rand", 2531 | "socket2 0.6.1", 2532 | "tokio", 2533 | "tokio-util", 2534 | "whoami", 2535 | ] 2536 | 2537 | [[package]] 2538 | name = "tokio-rustls" 2539 | version = "0.26.4" 2540 | source = "registry+https://github.com/rust-lang/crates.io-index" 2541 | checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" 2542 | dependencies = [ 2543 | "rustls", 2544 | "tokio", 2545 | ] 2546 | 2547 | [[package]] 2548 | name = "tokio-util" 2549 | version = "0.7.17" 2550 | source = "registry+https://github.com/rust-lang/crates.io-index" 2551 | checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" 2552 | dependencies = [ 2553 | "bytes", 2554 | "futures-core", 2555 | "futures-sink", 2556 | "pin-project-lite", 2557 | "tokio", 2558 | ] 2559 | 2560 | [[package]] 2561 | name = "tower" 2562 | version = "0.5.2" 2563 | source = "registry+https://github.com/rust-lang/crates.io-index" 2564 | checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 2565 | dependencies = [ 2566 | "futures-core", 2567 | "futures-util", 2568 | "pin-project-lite", 2569 | "sync_wrapper", 2570 | "tokio", 2571 | "tower-layer", 2572 | "tower-service", 2573 | "tracing", 2574 | ] 2575 | 2576 | [[package]] 2577 | name = "tower-http" 2578 | version = "0.6.6" 2579 | source = "registry+https://github.com/rust-lang/crates.io-index" 2580 | checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" 2581 | dependencies = [ 2582 | "bitflags", 2583 | "bytes", 2584 | "futures-core", 2585 | "futures-util", 2586 | "http", 2587 | "http-body", 2588 | "http-body-util", 2589 | "http-range-header", 2590 | "httpdate", 2591 | "iri-string", 2592 | "mime", 2593 | "mime_guess", 2594 | "percent-encoding", 2595 | "pin-project-lite", 2596 | "tokio", 2597 | "tokio-util", 2598 | "tower", 2599 | "tower-layer", 2600 | "tower-service", 2601 | "tracing", 2602 | ] 2603 | 2604 | [[package]] 2605 | name = "tower-layer" 2606 | version = "0.3.3" 2607 | source = "registry+https://github.com/rust-lang/crates.io-index" 2608 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 2609 | 2610 | [[package]] 2611 | name = "tower-service" 2612 | version = "0.3.3" 2613 | source = "registry+https://github.com/rust-lang/crates.io-index" 2614 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 2615 | 2616 | [[package]] 2617 | name = "tracing" 2618 | version = "0.1.41" 2619 | source = "registry+https://github.com/rust-lang/crates.io-index" 2620 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 2621 | dependencies = [ 2622 | "log", 2623 | "pin-project-lite", 2624 | "tracing-attributes", 2625 | "tracing-core", 2626 | ] 2627 | 2628 | [[package]] 2629 | name = "tracing-attributes" 2630 | version = "0.1.30" 2631 | source = "registry+https://github.com/rust-lang/crates.io-index" 2632 | checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" 2633 | dependencies = [ 2634 | "proc-macro2", 2635 | "quote", 2636 | "syn", 2637 | ] 2638 | 2639 | [[package]] 2640 | name = "tracing-core" 2641 | version = "0.1.34" 2642 | source = "registry+https://github.com/rust-lang/crates.io-index" 2643 | checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 2644 | dependencies = [ 2645 | "once_cell", 2646 | "valuable", 2647 | ] 2648 | 2649 | [[package]] 2650 | name = "tracing-log" 2651 | version = "0.2.0" 2652 | source = "registry+https://github.com/rust-lang/crates.io-index" 2653 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 2654 | dependencies = [ 2655 | "log", 2656 | "once_cell", 2657 | "tracing-core", 2658 | ] 2659 | 2660 | [[package]] 2661 | name = "tracing-subscriber" 2662 | version = "0.3.20" 2663 | source = "registry+https://github.com/rust-lang/crates.io-index" 2664 | checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" 2665 | dependencies = [ 2666 | "matchers", 2667 | "nu-ansi-term", 2668 | "once_cell", 2669 | "regex-automata", 2670 | "sharded-slab", 2671 | "smallvec", 2672 | "thread_local", 2673 | "tracing", 2674 | "tracing-core", 2675 | "tracing-log", 2676 | ] 2677 | 2678 | [[package]] 2679 | name = "try-lock" 2680 | version = "0.2.5" 2681 | source = "registry+https://github.com/rust-lang/crates.io-index" 2682 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 2683 | 2684 | [[package]] 2685 | name = "typenum" 2686 | version = "1.19.0" 2687 | source = "registry+https://github.com/rust-lang/crates.io-index" 2688 | checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" 2689 | 2690 | [[package]] 2691 | name = "unicase" 2692 | version = "2.8.1" 2693 | source = "registry+https://github.com/rust-lang/crates.io-index" 2694 | checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" 2695 | 2696 | [[package]] 2697 | name = "unicode-bidi" 2698 | version = "0.3.18" 2699 | source = "registry+https://github.com/rust-lang/crates.io-index" 2700 | checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" 2701 | 2702 | [[package]] 2703 | name = "unicode-ident" 2704 | version = "1.0.22" 2705 | source = "registry+https://github.com/rust-lang/crates.io-index" 2706 | checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 2707 | 2708 | [[package]] 2709 | name = "unicode-normalization" 2710 | version = "0.1.25" 2711 | source = "registry+https://github.com/rust-lang/crates.io-index" 2712 | checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" 2713 | dependencies = [ 2714 | "tinyvec", 2715 | ] 2716 | 2717 | [[package]] 2718 | name = "unicode-properties" 2719 | version = "0.1.4" 2720 | source = "registry+https://github.com/rust-lang/crates.io-index" 2721 | checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" 2722 | 2723 | [[package]] 2724 | name = "unsafe-libyaml" 2725 | version = "0.2.11" 2726 | source = "registry+https://github.com/rust-lang/crates.io-index" 2727 | checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" 2728 | 2729 | [[package]] 2730 | name = "untrusted" 2731 | version = "0.9.0" 2732 | source = "registry+https://github.com/rust-lang/crates.io-index" 2733 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 2734 | 2735 | [[package]] 2736 | name = "url" 2737 | version = "2.5.7" 2738 | source = "registry+https://github.com/rust-lang/crates.io-index" 2739 | checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" 2740 | dependencies = [ 2741 | "form_urlencoded", 2742 | "idna", 2743 | "percent-encoding", 2744 | "serde", 2745 | ] 2746 | 2747 | [[package]] 2748 | name = "urlencoding" 2749 | version = "2.1.3" 2750 | source = "registry+https://github.com/rust-lang/crates.io-index" 2751 | checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" 2752 | 2753 | [[package]] 2754 | name = "utf8-cstr" 2755 | version = "0.1.6" 2756 | source = "registry+https://github.com/rust-lang/crates.io-index" 2757 | checksum = "55bcbb425141152b10d5693095950b51c3745d019363fc2929ffd8f61449b628" 2758 | 2759 | [[package]] 2760 | name = "utf8_iter" 2761 | version = "1.0.4" 2762 | source = "registry+https://github.com/rust-lang/crates.io-index" 2763 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2764 | 2765 | [[package]] 2766 | name = "uuid" 2767 | version = "1.18.1" 2768 | source = "registry+https://github.com/rust-lang/crates.io-index" 2769 | checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" 2770 | dependencies = [ 2771 | "getrandom 0.3.4", 2772 | "js-sys", 2773 | "wasm-bindgen", 2774 | ] 2775 | 2776 | [[package]] 2777 | name = "valuable" 2778 | version = "0.1.1" 2779 | source = "registry+https://github.com/rust-lang/crates.io-index" 2780 | checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 2781 | 2782 | [[package]] 2783 | name = "vcpkg" 2784 | version = "0.2.15" 2785 | source = "registry+https://github.com/rust-lang/crates.io-index" 2786 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2787 | 2788 | [[package]] 2789 | name = "version_check" 2790 | version = "0.9.5" 2791 | source = "registry+https://github.com/rust-lang/crates.io-index" 2792 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 2793 | 2794 | [[package]] 2795 | name = "want" 2796 | version = "0.3.1" 2797 | source = "registry+https://github.com/rust-lang/crates.io-index" 2798 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 2799 | dependencies = [ 2800 | "try-lock", 2801 | ] 2802 | 2803 | [[package]] 2804 | name = "wasi" 2805 | version = "0.11.1+wasi-snapshot-preview1" 2806 | source = "registry+https://github.com/rust-lang/crates.io-index" 2807 | checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 2808 | 2809 | [[package]] 2810 | name = "wasip2" 2811 | version = "1.0.1+wasi-0.2.4" 2812 | source = "registry+https://github.com/rust-lang/crates.io-index" 2813 | checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" 2814 | dependencies = [ 2815 | "wit-bindgen", 2816 | ] 2817 | 2818 | [[package]] 2819 | name = "wasite" 2820 | version = "0.1.0" 2821 | source = "registry+https://github.com/rust-lang/crates.io-index" 2822 | checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" 2823 | 2824 | [[package]] 2825 | name = "wasm-bindgen" 2826 | version = "0.2.105" 2827 | source = "registry+https://github.com/rust-lang/crates.io-index" 2828 | checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" 2829 | dependencies = [ 2830 | "cfg-if", 2831 | "once_cell", 2832 | "rustversion", 2833 | "wasm-bindgen-macro", 2834 | "wasm-bindgen-shared", 2835 | ] 2836 | 2837 | [[package]] 2838 | name = "wasm-bindgen-futures" 2839 | version = "0.4.55" 2840 | source = "registry+https://github.com/rust-lang/crates.io-index" 2841 | checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" 2842 | dependencies = [ 2843 | "cfg-if", 2844 | "js-sys", 2845 | "once_cell", 2846 | "wasm-bindgen", 2847 | "web-sys", 2848 | ] 2849 | 2850 | [[package]] 2851 | name = "wasm-bindgen-macro" 2852 | version = "0.2.105" 2853 | source = "registry+https://github.com/rust-lang/crates.io-index" 2854 | checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" 2855 | dependencies = [ 2856 | "quote", 2857 | "wasm-bindgen-macro-support", 2858 | ] 2859 | 2860 | [[package]] 2861 | name = "wasm-bindgen-macro-support" 2862 | version = "0.2.105" 2863 | source = "registry+https://github.com/rust-lang/crates.io-index" 2864 | checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" 2865 | dependencies = [ 2866 | "bumpalo", 2867 | "proc-macro2", 2868 | "quote", 2869 | "syn", 2870 | "wasm-bindgen-shared", 2871 | ] 2872 | 2873 | [[package]] 2874 | name = "wasm-bindgen-shared" 2875 | version = "0.2.105" 2876 | source = "registry+https://github.com/rust-lang/crates.io-index" 2877 | checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" 2878 | dependencies = [ 2879 | "unicode-ident", 2880 | ] 2881 | 2882 | [[package]] 2883 | name = "wasm-streams" 2884 | version = "0.4.2" 2885 | source = "registry+https://github.com/rust-lang/crates.io-index" 2886 | checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" 2887 | dependencies = [ 2888 | "futures-util", 2889 | "js-sys", 2890 | "wasm-bindgen", 2891 | "wasm-bindgen-futures", 2892 | "web-sys", 2893 | ] 2894 | 2895 | [[package]] 2896 | name = "web-sys" 2897 | version = "0.3.82" 2898 | source = "registry+https://github.com/rust-lang/crates.io-index" 2899 | checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" 2900 | dependencies = [ 2901 | "js-sys", 2902 | "wasm-bindgen", 2903 | ] 2904 | 2905 | [[package]] 2906 | name = "web-time" 2907 | version = "1.1.0" 2908 | source = "registry+https://github.com/rust-lang/crates.io-index" 2909 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 2910 | dependencies = [ 2911 | "js-sys", 2912 | "wasm-bindgen", 2913 | ] 2914 | 2915 | [[package]] 2916 | name = "webpki-roots" 2917 | version = "1.0.4" 2918 | source = "registry+https://github.com/rust-lang/crates.io-index" 2919 | checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" 2920 | dependencies = [ 2921 | "rustls-pki-types", 2922 | ] 2923 | 2924 | [[package]] 2925 | name = "whoami" 2926 | version = "1.6.1" 2927 | source = "registry+https://github.com/rust-lang/crates.io-index" 2928 | checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" 2929 | dependencies = [ 2930 | "libredox", 2931 | "wasite", 2932 | "web-sys", 2933 | ] 2934 | 2935 | [[package]] 2936 | name = "widestring" 2937 | version = "1.2.1" 2938 | source = "registry+https://github.com/rust-lang/crates.io-index" 2939 | checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" 2940 | 2941 | [[package]] 2942 | name = "winapi" 2943 | version = "0.3.9" 2944 | source = "registry+https://github.com/rust-lang/crates.io-index" 2945 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2946 | dependencies = [ 2947 | "winapi-i686-pc-windows-gnu", 2948 | "winapi-x86_64-pc-windows-gnu", 2949 | ] 2950 | 2951 | [[package]] 2952 | name = "winapi-i686-pc-windows-gnu" 2953 | version = "0.4.0" 2954 | source = "registry+https://github.com/rust-lang/crates.io-index" 2955 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2956 | 2957 | [[package]] 2958 | name = "winapi-x86_64-pc-windows-gnu" 2959 | version = "0.4.0" 2960 | source = "registry+https://github.com/rust-lang/crates.io-index" 2961 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2962 | 2963 | [[package]] 2964 | name = "windows-core" 2965 | version = "0.62.2" 2966 | source = "registry+https://github.com/rust-lang/crates.io-index" 2967 | checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" 2968 | dependencies = [ 2969 | "windows-implement", 2970 | "windows-interface", 2971 | "windows-link", 2972 | "windows-result", 2973 | "windows-strings", 2974 | ] 2975 | 2976 | [[package]] 2977 | name = "windows-implement" 2978 | version = "0.60.2" 2979 | source = "registry+https://github.com/rust-lang/crates.io-index" 2980 | checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" 2981 | dependencies = [ 2982 | "proc-macro2", 2983 | "quote", 2984 | "syn", 2985 | ] 2986 | 2987 | [[package]] 2988 | name = "windows-interface" 2989 | version = "0.59.3" 2990 | source = "registry+https://github.com/rust-lang/crates.io-index" 2991 | checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" 2992 | dependencies = [ 2993 | "proc-macro2", 2994 | "quote", 2995 | "syn", 2996 | ] 2997 | 2998 | [[package]] 2999 | name = "windows-link" 3000 | version = "0.2.1" 3001 | source = "registry+https://github.com/rust-lang/crates.io-index" 3002 | checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 3003 | 3004 | [[package]] 3005 | name = "windows-registry" 3006 | version = "0.6.1" 3007 | source = "registry+https://github.com/rust-lang/crates.io-index" 3008 | checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" 3009 | dependencies = [ 3010 | "windows-link", 3011 | "windows-result", 3012 | "windows-strings", 3013 | ] 3014 | 3015 | [[package]] 3016 | name = "windows-result" 3017 | version = "0.4.1" 3018 | source = "registry+https://github.com/rust-lang/crates.io-index" 3019 | checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" 3020 | dependencies = [ 3021 | "windows-link", 3022 | ] 3023 | 3024 | [[package]] 3025 | name = "windows-strings" 3026 | version = "0.5.1" 3027 | source = "registry+https://github.com/rust-lang/crates.io-index" 3028 | checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" 3029 | dependencies = [ 3030 | "windows-link", 3031 | ] 3032 | 3033 | [[package]] 3034 | name = "windows-sys" 3035 | version = "0.48.0" 3036 | source = "registry+https://github.com/rust-lang/crates.io-index" 3037 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 3038 | dependencies = [ 3039 | "windows-targets 0.48.5", 3040 | ] 3041 | 3042 | [[package]] 3043 | name = "windows-sys" 3044 | version = "0.52.0" 3045 | source = "registry+https://github.com/rust-lang/crates.io-index" 3046 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 3047 | dependencies = [ 3048 | "windows-targets 0.52.6", 3049 | ] 3050 | 3051 | [[package]] 3052 | name = "windows-sys" 3053 | version = "0.60.2" 3054 | source = "registry+https://github.com/rust-lang/crates.io-index" 3055 | checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 3056 | dependencies = [ 3057 | "windows-targets 0.53.5", 3058 | ] 3059 | 3060 | [[package]] 3061 | name = "windows-sys" 3062 | version = "0.61.2" 3063 | source = "registry+https://github.com/rust-lang/crates.io-index" 3064 | checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 3065 | dependencies = [ 3066 | "windows-link", 3067 | ] 3068 | 3069 | [[package]] 3070 | name = "windows-targets" 3071 | version = "0.48.5" 3072 | source = "registry+https://github.com/rust-lang/crates.io-index" 3073 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 3074 | dependencies = [ 3075 | "windows_aarch64_gnullvm 0.48.5", 3076 | "windows_aarch64_msvc 0.48.5", 3077 | "windows_i686_gnu 0.48.5", 3078 | "windows_i686_msvc 0.48.5", 3079 | "windows_x86_64_gnu 0.48.5", 3080 | "windows_x86_64_gnullvm 0.48.5", 3081 | "windows_x86_64_msvc 0.48.5", 3082 | ] 3083 | 3084 | [[package]] 3085 | name = "windows-targets" 3086 | version = "0.52.6" 3087 | source = "registry+https://github.com/rust-lang/crates.io-index" 3088 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 3089 | dependencies = [ 3090 | "windows_aarch64_gnullvm 0.52.6", 3091 | "windows_aarch64_msvc 0.52.6", 3092 | "windows_i686_gnu 0.52.6", 3093 | "windows_i686_gnullvm 0.52.6", 3094 | "windows_i686_msvc 0.52.6", 3095 | "windows_x86_64_gnu 0.52.6", 3096 | "windows_x86_64_gnullvm 0.52.6", 3097 | "windows_x86_64_msvc 0.52.6", 3098 | ] 3099 | 3100 | [[package]] 3101 | name = "windows-targets" 3102 | version = "0.53.5" 3103 | source = "registry+https://github.com/rust-lang/crates.io-index" 3104 | checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" 3105 | dependencies = [ 3106 | "windows-link", 3107 | "windows_aarch64_gnullvm 0.53.1", 3108 | "windows_aarch64_msvc 0.53.1", 3109 | "windows_i686_gnu 0.53.1", 3110 | "windows_i686_gnullvm 0.53.1", 3111 | "windows_i686_msvc 0.53.1", 3112 | "windows_x86_64_gnu 0.53.1", 3113 | "windows_x86_64_gnullvm 0.53.1", 3114 | "windows_x86_64_msvc 0.53.1", 3115 | ] 3116 | 3117 | [[package]] 3118 | name = "windows_aarch64_gnullvm" 3119 | version = "0.48.5" 3120 | source = "registry+https://github.com/rust-lang/crates.io-index" 3121 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 3122 | 3123 | [[package]] 3124 | name = "windows_aarch64_gnullvm" 3125 | version = "0.52.6" 3126 | source = "registry+https://github.com/rust-lang/crates.io-index" 3127 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 3128 | 3129 | [[package]] 3130 | name = "windows_aarch64_gnullvm" 3131 | version = "0.53.1" 3132 | source = "registry+https://github.com/rust-lang/crates.io-index" 3133 | checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" 3134 | 3135 | [[package]] 3136 | name = "windows_aarch64_msvc" 3137 | version = "0.48.5" 3138 | source = "registry+https://github.com/rust-lang/crates.io-index" 3139 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 3140 | 3141 | [[package]] 3142 | name = "windows_aarch64_msvc" 3143 | version = "0.52.6" 3144 | source = "registry+https://github.com/rust-lang/crates.io-index" 3145 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 3146 | 3147 | [[package]] 3148 | name = "windows_aarch64_msvc" 3149 | version = "0.53.1" 3150 | source = "registry+https://github.com/rust-lang/crates.io-index" 3151 | checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" 3152 | 3153 | [[package]] 3154 | name = "windows_i686_gnu" 3155 | version = "0.48.5" 3156 | source = "registry+https://github.com/rust-lang/crates.io-index" 3157 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 3158 | 3159 | [[package]] 3160 | name = "windows_i686_gnu" 3161 | version = "0.52.6" 3162 | source = "registry+https://github.com/rust-lang/crates.io-index" 3163 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 3164 | 3165 | [[package]] 3166 | name = "windows_i686_gnu" 3167 | version = "0.53.1" 3168 | source = "registry+https://github.com/rust-lang/crates.io-index" 3169 | checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" 3170 | 3171 | [[package]] 3172 | name = "windows_i686_gnullvm" 3173 | version = "0.52.6" 3174 | source = "registry+https://github.com/rust-lang/crates.io-index" 3175 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 3176 | 3177 | [[package]] 3178 | name = "windows_i686_gnullvm" 3179 | version = "0.53.1" 3180 | source = "registry+https://github.com/rust-lang/crates.io-index" 3181 | checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" 3182 | 3183 | [[package]] 3184 | name = "windows_i686_msvc" 3185 | version = "0.48.5" 3186 | source = "registry+https://github.com/rust-lang/crates.io-index" 3187 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 3188 | 3189 | [[package]] 3190 | name = "windows_i686_msvc" 3191 | version = "0.52.6" 3192 | source = "registry+https://github.com/rust-lang/crates.io-index" 3193 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 3194 | 3195 | [[package]] 3196 | name = "windows_i686_msvc" 3197 | version = "0.53.1" 3198 | source = "registry+https://github.com/rust-lang/crates.io-index" 3199 | checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" 3200 | 3201 | [[package]] 3202 | name = "windows_x86_64_gnu" 3203 | version = "0.48.5" 3204 | source = "registry+https://github.com/rust-lang/crates.io-index" 3205 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 3206 | 3207 | [[package]] 3208 | name = "windows_x86_64_gnu" 3209 | version = "0.52.6" 3210 | source = "registry+https://github.com/rust-lang/crates.io-index" 3211 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 3212 | 3213 | [[package]] 3214 | name = "windows_x86_64_gnu" 3215 | version = "0.53.1" 3216 | source = "registry+https://github.com/rust-lang/crates.io-index" 3217 | checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" 3218 | 3219 | [[package]] 3220 | name = "windows_x86_64_gnullvm" 3221 | version = "0.48.5" 3222 | source = "registry+https://github.com/rust-lang/crates.io-index" 3223 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 3224 | 3225 | [[package]] 3226 | name = "windows_x86_64_gnullvm" 3227 | version = "0.52.6" 3228 | source = "registry+https://github.com/rust-lang/crates.io-index" 3229 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 3230 | 3231 | [[package]] 3232 | name = "windows_x86_64_gnullvm" 3233 | version = "0.53.1" 3234 | source = "registry+https://github.com/rust-lang/crates.io-index" 3235 | checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" 3236 | 3237 | [[package]] 3238 | name = "windows_x86_64_msvc" 3239 | version = "0.48.5" 3240 | source = "registry+https://github.com/rust-lang/crates.io-index" 3241 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 3242 | 3243 | [[package]] 3244 | name = "windows_x86_64_msvc" 3245 | version = "0.52.6" 3246 | source = "registry+https://github.com/rust-lang/crates.io-index" 3247 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 3248 | 3249 | [[package]] 3250 | name = "windows_x86_64_msvc" 3251 | version = "0.53.1" 3252 | source = "registry+https://github.com/rust-lang/crates.io-index" 3253 | checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" 3254 | 3255 | [[package]] 3256 | name = "winreg" 3257 | version = "0.50.0" 3258 | source = "registry+https://github.com/rust-lang/crates.io-index" 3259 | checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 3260 | dependencies = [ 3261 | "cfg-if", 3262 | "windows-sys 0.48.0", 3263 | ] 3264 | 3265 | [[package]] 3266 | name = "wit-bindgen" 3267 | version = "0.46.0" 3268 | source = "registry+https://github.com/rust-lang/crates.io-index" 3269 | checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" 3270 | 3271 | [[package]] 3272 | name = "writeable" 3273 | version = "0.6.2" 3274 | source = "registry+https://github.com/rust-lang/crates.io-index" 3275 | checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" 3276 | 3277 | [[package]] 3278 | name = "yoke" 3279 | version = "0.8.1" 3280 | source = "registry+https://github.com/rust-lang/crates.io-index" 3281 | checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" 3282 | dependencies = [ 3283 | "stable_deref_trait", 3284 | "yoke-derive", 3285 | "zerofrom", 3286 | ] 3287 | 3288 | [[package]] 3289 | name = "yoke-derive" 3290 | version = "0.8.1" 3291 | source = "registry+https://github.com/rust-lang/crates.io-index" 3292 | checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" 3293 | dependencies = [ 3294 | "proc-macro2", 3295 | "quote", 3296 | "syn", 3297 | "synstructure", 3298 | ] 3299 | 3300 | [[package]] 3301 | name = "zerocopy" 3302 | version = "0.8.27" 3303 | source = "registry+https://github.com/rust-lang/crates.io-index" 3304 | checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" 3305 | dependencies = [ 3306 | "zerocopy-derive", 3307 | ] 3308 | 3309 | [[package]] 3310 | name = "zerocopy-derive" 3311 | version = "0.8.27" 3312 | source = "registry+https://github.com/rust-lang/crates.io-index" 3313 | checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" 3314 | dependencies = [ 3315 | "proc-macro2", 3316 | "quote", 3317 | "syn", 3318 | ] 3319 | 3320 | [[package]] 3321 | name = "zerofrom" 3322 | version = "0.1.6" 3323 | source = "registry+https://github.com/rust-lang/crates.io-index" 3324 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 3325 | dependencies = [ 3326 | "zerofrom-derive", 3327 | ] 3328 | 3329 | [[package]] 3330 | name = "zerofrom-derive" 3331 | version = "0.1.6" 3332 | source = "registry+https://github.com/rust-lang/crates.io-index" 3333 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 3334 | dependencies = [ 3335 | "proc-macro2", 3336 | "quote", 3337 | "syn", 3338 | "synstructure", 3339 | ] 3340 | 3341 | [[package]] 3342 | name = "zeroize" 3343 | version = "1.8.2" 3344 | source = "registry+https://github.com/rust-lang/crates.io-index" 3345 | checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" 3346 | 3347 | [[package]] 3348 | name = "zerotrie" 3349 | version = "0.2.3" 3350 | source = "registry+https://github.com/rust-lang/crates.io-index" 3351 | checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" 3352 | dependencies = [ 3353 | "displaydoc", 3354 | "yoke", 3355 | "zerofrom", 3356 | ] 3357 | 3358 | [[package]] 3359 | name = "zerovec" 3360 | version = "0.11.5" 3361 | source = "registry+https://github.com/rust-lang/crates.io-index" 3362 | checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" 3363 | dependencies = [ 3364 | "yoke", 3365 | "zerofrom", 3366 | "zerovec-derive", 3367 | ] 3368 | 3369 | [[package]] 3370 | name = "zerovec-derive" 3371 | version = "0.11.2" 3372 | source = "registry+https://github.com/rust-lang/crates.io-index" 3373 | checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" 3374 | dependencies = [ 3375 | "proc-macro2", 3376 | "quote", 3377 | "syn", 3378 | ] 3379 | --------------------------------------------------------------------------------