HTTP transport: CBOR wire messages, HttpRemote client, oxy-server crate with bearer-token pushes, oxy serve 750251b21a96
- Author
- Andy McCall @andymccall <[email protected]>
- When
- · 2026-09-26 18:47 UTC
- Signature
- ✔ signed by Andy McCall
- Hash
750251b21a96e27ed790cb825a7cc67a5967c07a749eae6f6a3f8a3361045beb- Depends on
- 1983686fb7a7 20247a03a596 5b9c6ad45f6e e993a086c13f ff0af1ee299d
- Depended on by
- 835ceb9b3ea7 135bdcadec4a ed931d1d1102 0588de69a84d 569802ebae0e 4275ecb110f5 642fa213de80 80b069facc69 e02edccce98c e30c081a5064 b260109d27c5 f3811eb20bea d3b270909c78 b63fc7a77fc6 82202a4d0025 ee130dd9eef2 1ec6fdbc6bcb 6f06d9f912b4 67a4fe1aa2d5 028545dfa7a4 51d4862a16d8 4b9d06778581 68dcf21fe055 f05b84d1ad2d f53cbe7a204e f0a517d28237 efad40f9ac8e 68a924846379 e2a5edbf1395 cb8b8082fa73 d0bf2da15094 579afd107b9b c71a5777509a 2cd40ea1c04c f874dc8659c2 8d80356371c5 9f9e72b93e4e 6a0008781d5e bea00d4177c6 827281eed2ed b0461604ed98 14edbaaf5291 ff1f62fee654 2c75672fb2fe 598009a75f6c 6f8aeb996558 be451356e3ef 8c30d814f2cc f8c695764c95 11c958021e1a d42369abf180 3e5772ccf9da 37c299cec358 d82e44097323 be0161a88dd7 76f752503e7e 119bbf50cb86 7aa2dc9369e3 b0e64e81e4fb 05e71ed6cdf1 75167672ebcd e02bf0cd0533 c6aa5bd9d1c8 b3c5a59a875a 2b872d074ddb 28fc23d7706a 132b884b9a8d 2863456a0f6d 7421309f5372 135212de5370 d02e545dc265 83e0677b07fe 2b5ee371060e 982864068a59 fce44ae9ad29 02f1b14af1e2
- In bookmarks
- agents-in-readme bare-hashes carried-not-removed carry-identical doctor-trust identical-sides keyless-tags main merge-remote-bookmark only-help-103 paths-for-resolutions-and-amend push-notes show-stat subrepo-pin-state switch-says
- In tags
- v0.1.0 v0.10.0 v0.11.0 v0.2.0 v0.3.0 v0.3.1 v0.4.0 v0.4.1 v0.4.2 v0.4.3 v0.4.4 v0.4.5 v0.4.6 v0.5.0 v0.5.1 v0.6.0 v0.7.0 v0.8.0 v0.9.0
- Edits
- 71 atoms, 6 tree operations, 9 files
Browse the tree at this change
added crates/oxy-server/Cargo.toml +14 −0
| 1 | + | [package] |
|
| 2 | + | name = "oxy-server" |
|
| 3 | + | version.workspace = true |
|
| 4 | + | edition.workspace = true |
|
| 5 | + | license.workspace = true |
|
| 6 | + | description = "Reference HTTP server for the Oxygen wire protocol" |
|
| 7 | + | ||
| 8 | + | [dependencies] |
|
| 9 | + | oxy-core = { path = "../oxy-core" } |
|
| 10 | + | axum = "0.8" |
|
| 11 | + | tokio = { version = "1", features = ["rt-multi-thread", "net", "macros", "sync"] } |
|
| 12 | + | ||
| 13 | + | [dev-dependencies] |
|
| 14 | + | tempfile = "3" |
added crates/oxy-server/src/lib.rs +166 −0
| 1 | + | //! Reference HTTP server for the Oxygen wire protocol (spec §15.1, §16). |
|
| 2 | + | //! |
|
| 3 | + | //! Serves every repository directory under a root at `/{repo}/oxy/v1/...`. |
|
| 4 | + | //! TLS is expected to be terminated in front of this server for now; the |
|
| 5 | + | //! protocol's integrity does not depend on the channel (spec §15). |
|
| 6 | + | //! |
|
| 7 | + | //! Authentication: when bearer tokens are configured, `push` requires one; |
|
| 8 | + | //! reads are open. With no tokens configured everything is open (development). |
|
| 9 | + | ||
| 10 | + | use axum::body::Bytes; |
|
| 11 | + | use axum::extract::{Path, State}; |
|
| 12 | + | use axum::http::{header, HeaderMap, StatusCode}; |
|
| 13 | + | use axum::response::{IntoResponse, Response}; |
|
| 14 | + | use axum::routing::{get, post}; |
|
| 15 | + | use axum::Router; |
|
| 16 | + | use oxy_core::protocol::{wire, CONTENT_TYPE}; |
|
| 17 | + | use oxy_core::repo::Repo; |
|
| 18 | + | use std::collections::HashMap; |
|
| 19 | + | use std::path::PathBuf; |
|
| 20 | + | use std::sync::{Arc, Mutex}; |
|
| 21 | + | ||
| 22 | + | #[derive(Clone, Debug)] |
|
| 23 | + | pub struct ServerConfig { |
|
| 24 | + | /// Directory containing one repository per subdirectory. |
|
| 25 | + | pub root: PathBuf, |
|
| 26 | + | /// Bearer tokens allowed to push; empty means no authentication. |
|
| 27 | + | pub tokens: Vec<String>, |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | struct App { |
|
| 31 | + | cfg: ServerConfig, |
|
| 32 | + | /// One lock per repository so concurrent pushes serialize. |
|
| 33 | + | locks: Mutex<HashMap<String, Arc<Mutex<()>>>>, |
|
| 34 | + | } |
|
| 35 | + | ||
| 36 | + | type Shared = Arc<App>; |
|
| 37 | + | ||
| 38 | + | struct Failure(StatusCode, String); |
|
| 39 | + | ||
| 40 | + | impl IntoResponse for Failure { |
|
| 41 | + | fn into_response(self) -> Response { |
|
| 42 | + | (self.0, self.1).into_response() |
|
| 43 | + | } |
|
| 44 | + | } |
|
| 45 | + | ||
| 46 | + | impl From<oxy_core::repo::RepoError> for Failure { |
|
| 47 | + | fn from(e: oxy_core::repo::RepoError) -> Failure { |
|
| 48 | + | Failure(StatusCode::BAD_REQUEST, e.to_string()) |
|
| 49 | + | } |
|
| 50 | + | } |
|
| 51 | + | ||
| 52 | + | fn ok(body: Vec<u8>) -> Response { |
|
| 53 | + | ([(header::CONTENT_TYPE, CONTENT_TYPE)], body).into_response() |
|
| 54 | + | } |
|
| 55 | + | ||
| 56 | + | fn valid_repo_name(name: &str) -> bool { |
|
| 57 | + | !name.is_empty() |
|
| 58 | + | && !name.starts_with('.') |
|
| 59 | + | && name.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-')) |
|
| 60 | + | } |
|
| 61 | + | ||
| 62 | + | impl App { |
|
| 63 | + | fn open(&self, name: &str) -> Result<Repo, Failure> { |
|
| 64 | + | if !valid_repo_name(name) { |
|
| 65 | + | return Err(Failure(StatusCode::NOT_FOUND, "no such repository".into())); |
|
| 66 | + | } |
|
| 67 | + | let dir = self.cfg.root.join(name); |
|
| 68 | + | if !dir.join(".oxy").is_dir() { |
|
| 69 | + | return Err(Failure(StatusCode::NOT_FOUND, "no such repository".into())); |
|
| 70 | + | } |
|
| 71 | + | Repo::open(&dir).map_err(|e| Failure(StatusCode::INTERNAL_SERVER_ERROR, e.to_string())) |
|
| 72 | + | } |
|
| 73 | + | ||
| 74 | + | fn lock_for(&self, name: &str) -> Arc<Mutex<()>> { |
|
| 75 | + | self.locks.lock().unwrap().entry(name.to_string()).or_default().clone() |
|
| 76 | + | } |
|
| 77 | + | ||
| 78 | + | fn authorize_push(&self, headers: &HeaderMap) -> Result<(), Failure> { |
|
| 79 | + | if self.cfg.tokens.is_empty() { |
|
| 80 | + | return Ok(()); |
|
| 81 | + | } |
|
| 82 | + | let presented = headers |
|
| 83 | + | .get(header::AUTHORIZATION) |
|
| 84 | + | .and_then(|v| v.to_str().ok()) |
|
| 85 | + | .and_then(|v| v.strip_prefix("Bearer ")) |
|
| 86 | + | .map(str::trim); |
|
| 87 | + | match presented { |
|
| 88 | + | Some(t) if self.cfg.tokens.iter().any(|x| x == t) => Ok(()), |
|
| 89 | + | _ => Err(Failure(StatusCode::UNAUTHORIZED, "a valid bearer token is required to push".into())), |
|
| 90 | + | } |
|
| 91 | + | } |
|
| 92 | + | } |
|
| 93 | + | ||
| 94 | + | async fn blocking<T: Send + 'static>(f: impl FnOnce() -> Result<T, Failure> + Send + 'static) -> Result<T, Failure> { |
|
| 95 | + | tokio::task::spawn_blocking(f).await.map_err(|e| Failure(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? |
|
| 96 | + | } |
|
| 97 | + | ||
| 98 | + | async fn capabilities(State(app): State<Shared>, Path(repo): Path<String>) -> Result<Response, Failure> { |
|
| 99 | + | blocking(move || { |
|
| 100 | + | let r = app.open(&repo)?; |
|
| 101 | + | Ok(ok(wire::encode_capabilities(r.id()?))) |
|
| 102 | + | }) |
|
| 103 | + | .await |
|
| 104 | + | } |
|
| 105 | + | ||
| 106 | + | async fn refs(State(app): State<Shared>, Path(repo): Path<String>) -> Result<Response, Failure> { |
|
| 107 | + | blocking(move || { |
|
| 108 | + | let r = app.open(&repo)?; |
|
| 109 | + | Ok(ok(wire::encode_refs(r.id()?, &r.remote_refs()?))) |
|
| 110 | + | }) |
|
| 111 | + | .await |
|
| 112 | + | } |
|
| 113 | + | ||
| 114 | + | async fn negotiate(State(app): State<Shared>, Path(repo): Path<String>, body: Bytes) -> Result<Response, Failure> { |
|
| 115 | + | blocking(move || { |
|
| 116 | + | let r = app.open(&repo)?; |
|
| 117 | + | let (have, want) = wire::decode_negotiate(&body)?; |
|
| 118 | + | Ok(ok(wire::encode_hashes(&r.negotiate(&have, &want)?))) |
|
| 119 | + | }) |
|
| 120 | + | .await |
|
| 121 | + | } |
|
| 122 | + | ||
| 123 | + | async fn fetch(State(app): State<Shared>, Path(repo): Path<String>, body: Bytes) -> Result<Response, Failure> { |
|
| 124 | + | blocking(move || { |
|
| 125 | + | let r = app.open(&repo)?; |
|
| 126 | + | let changes = wire::decode_hashes(&body)?; |
|
| 127 | + | Ok(ok(wire::encode_bundle(&r.bundle_for(&changes)?))) |
|
| 128 | + | }) |
|
| 129 | + | .await |
|
| 130 | + | } |
|
| 131 | + | ||
| 132 | + | async fn push(State(app): State<Shared>, Path(repo): Path<String>, headers: HeaderMap, body: Bytes) -> Result<Response, Failure> { |
|
| 133 | + | app.authorize_push(&headers)?; |
|
| 134 | + | blocking(move || { |
|
| 135 | + | let r = app.open(&repo)?; |
|
| 136 | + | let (bundle, updates) = wire::decode_push(&body)?; |
|
| 137 | + | let lock = app.lock_for(&repo); |
|
| 138 | + | let _guard = lock.lock().unwrap(); |
|
| 139 | + | Ok(ok(wire::encode_push_response(&r.receive_push(bundle, updates)?))) |
|
| 140 | + | }) |
|
| 141 | + | .await |
|
| 142 | + | } |
|
| 143 | + | ||
| 144 | + | pub fn router(cfg: ServerConfig) -> Router { |
|
| 145 | + | let app: Shared = Arc::new(App { cfg, locks: Mutex::new(HashMap::new()) }); |
|
| 146 | + | Router::new() |
|
| 147 | + | .route("/{repo}/oxy/v1/capabilities", get(capabilities)) |
|
| 148 | + | .route("/{repo}/oxy/v1/refs", get(refs)) |
|
| 149 | + | .route("/{repo}/oxy/v1/negotiate", post(negotiate)) |
|
| 150 | + | .route("/{repo}/oxy/v1/fetch", post(fetch)) |
|
| 151 | + | .route("/{repo}/oxy/v1/push", post(push)) |
|
| 152 | + | .with_state(app) |
|
| 153 | + | } |
|
| 154 | + | ||
| 155 | + | /// Serve on an already-bound listener until the process ends. |
|
| 156 | + | pub async fn serve(listener: std::net::TcpListener, cfg: ServerConfig) -> std::io::Result<()> { |
|
| 157 | + | listener.set_nonblocking(true)?; |
|
| 158 | + | let listener = tokio::net::TcpListener::from_std(listener)?; |
|
| 159 | + | axum::serve(listener, router(cfg)).await |
|
| 160 | + | } |
|
| 161 | + | ||
| 162 | + | /// Build a runtime and serve; for the CLI and for tests on a background thread. |
|
| 163 | + | pub fn run_blocking(listener: std::net::TcpListener, cfg: ServerConfig) -> std::io::Result<()> { |
|
| 164 | + | let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?; |
|
| 165 | + | rt.block_on(serve(listener, cfg)) |
|
| 166 | + | } |
added crates/oxy-server/tests/http.rs +89 −0
| 1 | + | //! End-to-end: a real server on a loopback port, clients over HTTP. |
|
| 2 | + | ||
| 3 | + | use oxy_core::repo::{Repo, SignatureStatus}; |
|
| 4 | + | use std::fs; |
|
| 5 | + | use std::net::TcpListener; |
|
| 6 | + | use std::path::Path; |
|
| 7 | + | ||
| 8 | + | fn write(root: &Path, rel: &str, content: &[u8]) { |
|
| 9 | + | let p = root.join(rel); |
|
| 10 | + | fs::create_dir_all(p.parent().unwrap()).unwrap(); |
|
| 11 | + | fs::write(p, content).unwrap(); |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | fn start(root: &Path, tokens: Vec<String>) -> String { |
|
| 15 | + | let listener = TcpListener::bind("127.0.0.1:0").unwrap(); |
|
| 16 | + | let port = listener.local_addr().unwrap().port(); |
|
| 17 | + | let cfg = oxy_server::ServerConfig { root: root.to_path_buf(), tokens }; |
|
| 18 | + | std::thread::spawn(move || oxy_server::run_blocking(listener, cfg).unwrap()); |
|
| 19 | + | format!("http://127.0.0.1:{port}") |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | #[test] |
|
| 23 | + | fn clone_push_pull_over_http() { |
|
| 24 | + | let dir = tempfile::tempdir().unwrap(); |
|
| 25 | + | let root = dir.path(); |
|
| 26 | + | let served = root.join("served"); |
|
| 27 | + | Repo::init(&served.join("origin")).unwrap(); |
|
| 28 | + | let base = start(&served, vec![]); |
|
| 29 | + | let url = format!("{base}/origin"); |
|
| 30 | + | ||
| 31 | + | let mut a = Repo::clone(&url, &root.join("a")).unwrap().with_keys_dir(root.join("keys")); |
|
| 32 | + | let (key, _) = a.create_identity("Ada", "[email protected]").unwrap(); |
|
| 33 | + | write(&root.join("a"), "f.txt", b"hello\n"); |
|
| 34 | + | let h1 = a.commit("hello", &[]).unwrap(); |
|
| 35 | + | let r = a.push("origin", false).unwrap(); |
|
| 36 | + | assert!(r[0].ok, "{}", r[0].reason); |
|
| 37 | + | ||
| 38 | + | let origin = Repo::open(&served.join("origin")).unwrap(); |
|
| 39 | + | assert_eq!(origin.bookmark_heads().unwrap(), vec![h1]); |
|
| 40 | + | assert!(matches!(origin.authenticate_change(&h1).unwrap(), SignatureStatus::Valid { key: k, sealed: true, .. } if k == key)); |
|
| 41 | + | ||
| 42 | + | let b = Repo::clone(&url, &root.join("b")).unwrap(); |
|
| 43 | + | assert_eq!(fs::read(root.join("b/f.txt")).unwrap(), b"hello\n"); |
|
| 44 | + | write(&root.join("b"), "g.txt", b"g\n"); |
|
| 45 | + | let h2 = b.commit("g", &[]).unwrap(); |
|
| 46 | + | assert!(b.push("origin", false).unwrap()[0].ok); |
|
| 47 | + | ||
| 48 | + | let pr = a.pull("origin").unwrap(); |
|
| 49 | + | assert!(pr.changed && pr.checked_out); |
|
| 50 | + | assert_eq!(fs::read(root.join("a/g.txt")).unwrap(), b"g\n"); |
|
| 51 | + | let mut heads = a.bookmark_heads().unwrap(); |
|
| 52 | + | heads.sort(); |
|
| 53 | + | let mut want = vec![h1, h2]; |
|
| 54 | + | want.sort(); |
|
| 55 | + | assert_eq!(heads, want); |
|
| 56 | + | ||
| 57 | + | // Unknown repositories and names the server refuses are surfaced as errors. |
|
| 58 | + | let err = Repo::clone(&format!("{base}/nope"), &root.join("c")).unwrap_err().to_string(); |
|
| 59 | + | assert!(err.contains("404"), "{err}"); |
|
| 60 | + | fs::create_dir_all(served.join(".hidden/.oxy")).unwrap(); |
|
| 61 | + | assert!(Repo::clone(&format!("{base}/.hidden"), &root.join("d")).unwrap_err().to_string().contains("404")); |
|
| 62 | + | } |
|
| 63 | + | ||
| 64 | + | #[test] |
|
| 65 | + | fn push_requires_a_token_when_configured() { |
|
| 66 | + | let dir = tempfile::tempdir().unwrap(); |
|
| 67 | + | let root = dir.path(); |
|
| 68 | + | let served = root.join("served"); |
|
| 69 | + | Repo::init(&served.join("origin")).unwrap(); |
|
| 70 | + | let base = start(&served, vec!["s3cret".into()]); |
|
| 71 | + | let url = format!("{base}/origin"); |
|
| 72 | + | ||
| 73 | + | // Reads are open. |
|
| 74 | + | let mut a = Repo::clone(&url, &root.join("a")).unwrap(); |
|
| 75 | + | write(&root.join("a"), "f.txt", b"x\n"); |
|
| 76 | + | a.commit("x", &[]).unwrap(); |
|
| 77 | + | let err = a.push("origin", false).unwrap_err().to_string(); |
|
| 78 | + | assert!(err.contains("401"), "{err}"); |
|
| 79 | + | ||
| 80 | + | a.config.set("remote.origin.token", "wrong").unwrap(); |
|
| 81 | + | a.save_config().unwrap(); |
|
| 82 | + | assert!(a.push("origin", false).unwrap_err().to_string().contains("401")); |
|
| 83 | + | ||
| 84 | + | a.config.set("remote.origin.token", "s3cret").unwrap(); |
|
| 85 | + | a.save_config().unwrap(); |
|
| 86 | + | assert!(a.push("origin", false).unwrap()[0].ok); |
|
| 87 | + | let origin = Repo::open(&served.join("origin")).unwrap(); |
|
| 88 | + | assert_eq!(origin.bookmark_heads().unwrap().len(), 1); |
|
| 89 | + | } |
modified Cargo.lock +877 −6
| … | 2 unchanged lines |
||
| 3 | 3 | version = 4 |
|
| 4 | 4 | ||
| 5 | 5 | [[package]] |
|
| 6 | + | name = "adler2" |
|
| 7 | + | version = "2.0.1" |
|
| 8 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 9 | + | checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" |
|
| 10 | + | ||
| 11 | + | [[package]] |
|
| 6 | 12 | name = "aho-corasick" |
|
| 7 | 13 | version = "1.1.5" |
|
| 8 | 14 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 38 unchanged lines |
||
| 47 | 53 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 48 | 54 | checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" |
|
| 49 | 55 | dependencies = [ |
|
| 50 | - | "windows-sys", |
|
| 56 | + | "windows-sys 0.61.2", |
|
| 51 | 57 | ] |
|
| 52 | 58 | ||
| 53 | 59 | [[package]] |
|
| … | 4 unchanged lines |
||
| 58 | 64 | dependencies = [ |
|
| 59 | 65 | "anstyle", |
|
| 60 | 66 | "once_cell_polyfill", |
|
| 61 | - | "windows-sys", |
|
| 67 | + | "windows-sys 0.61.2", |
|
| 62 | 68 | ] |
|
| 63 | 69 | ||
| 64 | 70 | [[package]] |
|
| … | 9 unchanged lines |
||
| 74 | 80 | checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" |
|
| 75 | 81 | ||
| 76 | 82 | [[package]] |
|
| 83 | + | name = "atomic-waker" |
|
| 84 | + | version = "1.1.2" |
|
| 85 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 86 | + | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" |
|
| 87 | + | ||
| 88 | + | [[package]] |
|
| 77 | 89 | name = "autocfg" |
|
| 78 | 90 | version = "1.5.1" |
|
| 79 | 91 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 80 | 92 | checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" |
|
| 81 | 93 | ||
| 82 | 94 | [[package]] |
|
| 95 | + | name = "axum" |
|
| 96 | + | version = "0.8.9" |
|
| 97 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 98 | + | checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" |
|
| 99 | + | dependencies = [ |
|
| 100 | + | "axum-core", |
|
| 101 | + | "bytes", |
|
| 102 | + | "form_urlencoded", |
|
| 103 | + | "futures-util", |
|
| 104 | + | "http", |
|
| 105 | + | "http-body", |
|
| 106 | + | "http-body-util", |
|
| 107 | + | "hyper", |
|
| 108 | + | "hyper-util", |
|
| 109 | + | "itoa", |
|
| 110 | + | "matchit", |
|
| 111 | + | "memchr", |
|
| 112 | + | "mime", |
|
| 113 | + | "percent-encoding", |
|
| 114 | + | "pin-project-lite", |
|
| 115 | + | "serde_core", |
|
| 116 | + | "serde_json", |
|
| 117 | + | "serde_path_to_error", |
|
| 118 | + | "serde_urlencoded", |
|
| 119 | + | "sync_wrapper", |
|
| 120 | + | "tokio", |
|
| 121 | + | "tower", |
|
| 122 | + | "tower-layer", |
|
| 123 | + | "tower-service", |
|
| 124 | + | "tracing", |
|
| 125 | + | ] |
|
| 126 | + | ||
| 127 | + | [[package]] |
|
| 128 | + | name = "axum-core" |
|
| 129 | + | version = "0.5.6" |
|
| 130 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 131 | + | checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" |
|
| 132 | + | dependencies = [ |
|
| 133 | + | "bytes", |
|
| 134 | + | "futures-core", |
|
| 135 | + | "http", |
|
| 136 | + | "http-body", |
|
| 137 | + | "http-body-util", |
|
| 138 | + | "mime", |
|
| 139 | + | "pin-project-lite", |
|
| 140 | + | "sync_wrapper", |
|
| 141 | + | "tower-layer", |
|
| 142 | + | "tower-service", |
|
| 143 | + | "tracing", |
|
| 144 | + | ] |
|
| 145 | + | ||
| 146 | + | [[package]] |
|
| 147 | + | name = "base64" |
|
| 148 | + | version = "0.22.1" |
|
| 149 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 150 | + | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" |
|
| 151 | + | ||
| 152 | + | [[package]] |
|
| 83 | 153 | name = "base64ct" |
|
| 84 | 154 | version = "1.8.3" |
|
| 85 | 155 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 38 unchanged lines |
||
| 124 | 194 | ] |
|
| 125 | 195 | ||
| 126 | 196 | [[package]] |
|
| 197 | + | name = "bytes" |
|
| 198 | + | version = "1.12.1" |
|
| 199 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 200 | + | checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" |
|
| 201 | + | ||
| 202 | + | [[package]] |
|
| 127 | 203 | name = "cc" |
|
| 128 | 204 | version = "1.5.1" |
|
| 129 | 205 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 83 unchanged lines |
||
| 213 | 289 | ] |
|
| 214 | 290 | ||
| 215 | 291 | [[package]] |
|
| 292 | + | name = "crc32fast" |
|
| 293 | + | version = "1.5.2" |
|
| 294 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 295 | + | checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" |
|
| 296 | + | dependencies = [ |
|
| 297 | + | "cfg-if", |
|
| 298 | + | ] |
|
| 299 | + | ||
| 300 | + | [[package]] |
|
| 216 | 301 | name = "crossbeam-deque" |
|
| 217 | 302 | version = "0.8.8" |
|
| 218 | 303 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 86 unchanged lines |
||
| 305 | 390 | ] |
|
| 306 | 391 | ||
| 307 | 392 | [[package]] |
|
| 393 | + | name = "displaydoc" |
|
| 394 | + | version = "0.2.7" |
|
| 395 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 396 | + | checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" |
|
| 397 | + | dependencies = [ |
|
| 398 | + | "proc-macro2", |
|
| 399 | + | "quote", |
|
| 400 | + | "syn 3.0.6", |
|
| 401 | + | ] |
|
| 402 | + | ||
| 403 | + | [[package]] |
|
| 308 | 404 | name = "ed25519" |
|
| 309 | 405 | version = "3.0.0" |
|
| 310 | 406 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 22 unchanged lines |
||
| 333 | 429 | checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" |
|
| 334 | 430 | dependencies = [ |
|
| 335 | 431 | "libc", |
|
| 336 | - | "windows-sys", |
|
| 432 | + | "windows-sys 0.61.2", |
|
| 337 | 433 | ] |
|
| 338 | 434 | ||
| 339 | 435 | [[package]] |
|
| … | 15 unchanged lines |
||
| 355 | 451 | checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" |
|
| 356 | 452 | ||
| 357 | 453 | [[package]] |
|
| 454 | + | name = "flate2" |
|
| 455 | + | version = "1.1.10" |
|
| 456 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 457 | + | checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" |
|
| 458 | + | dependencies = [ |
|
| 459 | + | "crc32fast", |
|
| 460 | + | "miniz_oxide", |
|
| 461 | + | "zlib-rs", |
|
| 462 | + | ] |
|
| 463 | + | ||
| 464 | + | [[package]] |
|
| 465 | + | name = "form_urlencoded" |
|
| 466 | + | version = "1.2.2" |
|
| 467 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 468 | + | checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" |
|
| 469 | + | dependencies = [ |
|
| 470 | + | "percent-encoding", |
|
| 471 | + | ] |
|
| 472 | + | ||
| 473 | + | [[package]] |
|
| 474 | + | name = "futures-channel" |
|
| 475 | + | version = "0.3.34" |
|
| 476 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 477 | + | checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" |
|
| 478 | + | dependencies = [ |
|
| 479 | + | "futures-core", |
|
| 480 | + | ] |
|
| 481 | + | ||
| 482 | + | [[package]] |
|
| 483 | + | name = "futures-core" |
|
| 484 | + | version = "0.3.34" |
|
| 485 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 486 | + | checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" |
|
| 487 | + | ||
| 488 | + | [[package]] |
|
| 489 | + | name = "futures-task" |
|
| 490 | + | version = "0.3.34" |
|
| 491 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 492 | + | checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" |
|
| 493 | + | ||
| 494 | + | [[package]] |
|
| 495 | + | name = "futures-util" |
|
| 496 | + | version = "0.3.34" |
|
| 497 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 498 | + | checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" |
|
| 499 | + | dependencies = [ |
|
| 500 | + | "futures-core", |
|
| 501 | + | "futures-task", |
|
| 502 | + | "pin-project-lite", |
|
| 503 | + | "slab", |
|
| 504 | + | ] |
|
| 505 | + | ||
| 506 | + | [[package]] |
|
| 358 | 507 | name = "getrandom" |
|
| 359 | 508 | version = "0.2.17" |
|
| 360 | 509 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 42 unchanged lines |
||
| 403 | 552 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" |
|
| 404 | 553 | ||
| 405 | 554 | [[package]] |
|
| 555 | + | name = "http" |
|
| 556 | + | version = "1.5.0" |
|
| 557 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 558 | + | checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" |
|
| 559 | + | dependencies = [ |
|
| 560 | + | "bytes", |
|
| 561 | + | "itoa", |
|
| 562 | + | ] |
|
| 563 | + | ||
| 564 | + | [[package]] |
|
| 565 | + | name = "http-body" |
|
| 566 | + | version = "1.1.0" |
|
| 567 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 568 | + | checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" |
|
| 569 | + | dependencies = [ |
|
| 570 | + | "bytes", |
|
| 571 | + | "http", |
|
| 572 | + | ] |
|
| 573 | + | ||
| 574 | + | [[package]] |
|
| 575 | + | name = "http-body-util" |
|
| 576 | + | version = "0.1.5" |
|
| 577 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 578 | + | checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" |
|
| 579 | + | dependencies = [ |
|
| 580 | + | "bytes", |
|
| 581 | + | "futures-core", |
|
| 582 | + | "http", |
|
| 583 | + | "http-body", |
|
| 584 | + | "pin-project-lite", |
|
| 585 | + | ] |
|
| 586 | + | ||
| 587 | + | [[package]] |
|
| 588 | + | name = "httparse" |
|
| 589 | + | version = "1.10.1" |
|
| 590 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 591 | + | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" |
|
| 592 | + | ||
| 593 | + | [[package]] |
|
| 594 | + | name = "httpdate" |
|
| 595 | + | version = "1.0.3" |
|
| 596 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 597 | + | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" |
|
| 598 | + | ||
| 599 | + | [[package]] |
|
| 406 | 600 | name = "hybrid-array" |
|
| 407 | 601 | version = "0.4.15" |
|
| 408 | 602 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 4 unchanged lines |
||
| 413 | 607 | ] |
|
| 414 | 608 | ||
| 415 | 609 | [[package]] |
|
| 610 | + | name = "hyper" |
|
| 611 | + | version = "1.11.1" |
|
| 612 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 613 | + | checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" |
|
| 614 | + | dependencies = [ |
|
| 615 | + | "atomic-waker", |
|
| 616 | + | "bytes", |
|
| 617 | + | "futures-channel", |
|
| 618 | + | "futures-core", |
|
| 619 | + | "http", |
|
| 620 | + | "http-body", |
|
| 621 | + | "httparse", |
|
| 622 | + | "httpdate", |
|
| 623 | + | "itoa", |
|
| 624 | + | "pin-project-lite", |
|
| 625 | + | "smallvec", |
|
| 626 | + | "tokio", |
|
| 627 | + | ] |
|
| 628 | + | ||
| 629 | + | [[package]] |
|
| 630 | + | name = "hyper-util" |
|
| 631 | + | version = "0.1.21" |
|
| 632 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 633 | + | checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" |
|
| 634 | + | dependencies = [ |
|
| 635 | + | "bytes", |
|
| 636 | + | "http", |
|
| 637 | + | "http-body", |
|
| 638 | + | "hyper", |
|
| 639 | + | "pin-project-lite", |
|
| 640 | + | "tokio", |
|
| 641 | + | "tower-service", |
|
| 642 | + | ] |
|
| 643 | + | ||
| 644 | + | [[package]] |
|
| 645 | + | name = "icu_collections" |
|
| 646 | + | version = "2.3.0" |
|
| 647 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 648 | + | checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" |
|
| 649 | + | dependencies = [ |
|
| 650 | + | "displaydoc", |
|
| 651 | + | "potential_utf", |
|
| 652 | + | "utf8_iter", |
|
| 653 | + | "yoke", |
|
| 654 | + | "zerofrom", |
|
| 655 | + | "zerovec", |
|
| 656 | + | ] |
|
| 657 | + | ||
| 658 | + | [[package]] |
|
| 659 | + | name = "icu_locale_core" |
|
| 660 | + | version = "2.3.0" |
|
| 661 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 662 | + | checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" |
|
| 663 | + | dependencies = [ |
|
| 664 | + | "displaydoc", |
|
| 665 | + | "litemap", |
|
| 666 | + | "tinystr", |
|
| 667 | + | "writeable", |
|
| 668 | + | "zerovec", |
|
| 669 | + | ] |
|
| 670 | + | ||
| 671 | + | [[package]] |
|
| 672 | + | name = "icu_normalizer" |
|
| 673 | + | version = "2.3.0" |
|
| 674 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 675 | + | checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" |
|
| 676 | + | dependencies = [ |
|
| 677 | + | "icu_collections", |
|
| 678 | + | "icu_normalizer_data", |
|
| 679 | + | "icu_properties", |
|
| 680 | + | "icu_provider", |
|
| 681 | + | "smallvec", |
|
| 682 | + | "zerovec", |
|
| 683 | + | ] |
|
| 684 | + | ||
| 685 | + | [[package]] |
|
| 686 | + | name = "icu_normalizer_data" |
|
| 687 | + | version = "2.3.0" |
|
| 688 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 689 | + | checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" |
|
| 690 | + | ||
| 691 | + | [[package]] |
|
| 692 | + | name = "icu_properties" |
|
| 693 | + | version = "2.3.0" |
|
| 694 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 695 | + | checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" |
|
| 696 | + | dependencies = [ |
|
| 697 | + | "displaydoc", |
|
| 698 | + | "icu_collections", |
|
| 699 | + | "icu_locale_core", |
|
| 700 | + | "icu_properties_data", |
|
| 701 | + | "icu_provider", |
|
| 702 | + | "zerotrie", |
|
| 703 | + | "zerovec", |
|
| 704 | + | ] |
|
| 705 | + | ||
| 706 | + | [[package]] |
|
| 707 | + | name = "icu_properties_data" |
|
| 708 | + | version = "2.3.0" |
|
| 709 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 710 | + | checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" |
|
| 711 | + | ||
| 712 | + | [[package]] |
|
| 713 | + | name = "icu_provider" |
|
| 714 | + | version = "2.3.1" |
|
| 715 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 716 | + | checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" |
|
| 717 | + | dependencies = [ |
|
| 718 | + | "displaydoc", |
|
| 719 | + | "icu_locale_core", |
|
| 720 | + | "writeable", |
|
| 721 | + | "yoke", |
|
| 722 | + | "zerofrom", |
|
| 723 | + | "zerotrie", |
|
| 724 | + | "zerovec", |
|
| 725 | + | ] |
|
| 726 | + | ||
| 727 | + | [[package]] |
|
| 728 | + | name = "idna" |
|
| 729 | + | version = "1.1.0" |
|
| 730 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 731 | + | checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" |
|
| 732 | + | dependencies = [ |
|
| 733 | + | "idna_adapter", |
|
| 734 | + | "smallvec", |
|
| 735 | + | "utf8_iter", |
|
| 736 | + | ] |
|
| 737 | + | ||
| 738 | + | [[package]] |
|
| 739 | + | name = "idna_adapter" |
|
| 740 | + | version = "1.2.2" |
|
| 741 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 742 | + | checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" |
|
| 743 | + | dependencies = [ |
|
| 744 | + | "icu_normalizer", |
|
| 745 | + | "icu_properties", |
|
| 746 | + | ] |
|
| 747 | + | ||
| 748 | + | [[package]] |
|
| 416 | 749 | name = "ignore" |
|
| 417 | 750 | version = "0.4.33" |
|
| 418 | 751 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 16 unchanged lines |
||
| 435 | 768 | checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" |
|
| 436 | 769 | ||
| 437 | 770 | [[package]] |
|
| 771 | + | name = "itoa" |
|
| 772 | + | version = "1.0.18" |
|
| 773 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 774 | + | checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" |
|
| 775 | + | ||
| 776 | + | [[package]] |
|
| 438 | 777 | name = "keccak" |
|
| 439 | 778 | version = "0.2.2" |
|
| 440 | 779 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 16 unchanged lines |
||
| 457 | 796 | checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" |
|
| 458 | 797 | ||
| 459 | 798 | [[package]] |
|
| 799 | + | name = "litemap" |
|
| 800 | + | version = "0.8.3" |
|
| 801 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 802 | + | checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" |
|
| 803 | + | ||
| 804 | + | [[package]] |
|
| 460 | 805 | name = "log" |
|
| 461 | 806 | version = "0.4.34" |
|
| 462 | 807 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 463 | 808 | checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" |
|
| 464 | 809 | ||
| 465 | 810 | [[package]] |
|
| 811 | + | name = "matchit" |
|
| 812 | + | version = "0.8.4" |
|
| 813 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 814 | + | checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" |
|
| 815 | + | ||
| 816 | + | [[package]] |
|
| 466 | 817 | name = "memchr" |
|
| 467 | 818 | version = "2.8.3" |
|
| 468 | 819 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 469 | 820 | checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" |
|
| 470 | 821 | ||
| 471 | 822 | [[package]] |
|
| 823 | + | name = "mime" |
|
| 824 | + | version = "0.3.17" |
|
| 825 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 826 | + | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" |
|
| 827 | + | ||
| 828 | + | [[package]] |
|
| 829 | + | name = "miniz_oxide" |
|
| 830 | + | version = "0.9.1" |
|
| 831 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 832 | + | checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" |
|
| 833 | + | dependencies = [ |
|
| 834 | + | "adler2", |
|
| 835 | + | "simd-adler32", |
|
| 836 | + | ] |
|
| 837 | + | ||
| 838 | + | [[package]] |
|
| 839 | + | name = "mio" |
|
| 840 | + | version = "1.2.3" |
|
| 841 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 842 | + | checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" |
|
| 843 | + | dependencies = [ |
|
| 844 | + | "libc", |
|
| 845 | + | "wasi", |
|
| 846 | + | "windows-sys 0.61.2", |
|
| 847 | + | ] |
|
| 848 | + | ||
| 849 | + | [[package]] |
|
| 472 | 850 | name = "ml-dsa" |
|
| 473 | 851 | version = "0.1.1" |
|
| 474 | 852 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 49 unchanged lines |
||
| 524 | 902 | "clap", |
|
| 525 | 903 | "libc", |
|
| 526 | 904 | "oxy-core", |
|
| 905 | + | "oxy-server", |
|
| 527 | 906 | "tempfile", |
|
| 528 | 907 | ] |
|
| 529 | 908 | ||
| … | 10 unchanged lines |
||
| 540 | 919 | "similar", |
|
| 541 | 920 | "tempfile", |
|
| 542 | 921 | "thiserror", |
|
| 922 | + | "ureq", |
|
| 923 | + | ] |
|
| 924 | + | ||
| 925 | + | [[package]] |
|
| 926 | + | name = "oxy-server" |
|
| 927 | + | version = "0.1.0" |
|
| 928 | + | dependencies = [ |
|
| 929 | + | "axum", |
|
| 930 | + | "oxy-core", |
|
| 931 | + | "tempfile", |
|
| 932 | + | "tokio", |
|
| 543 | 933 | ] |
|
| 544 | 934 | ||
| 545 | 935 | [[package]] |
|
| 936 | + | name = "percent-encoding" |
|
| 937 | + | version = "2.3.2" |
|
| 938 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 939 | + | checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" |
|
| 940 | + | ||
| 941 | + | [[package]] |
|
| 942 | + | name = "pin-project-lite" |
|
| 943 | + | version = "0.2.17" |
|
| 944 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 945 | + | checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" |
|
| 946 | + | ||
| 947 | + | [[package]] |
|
| 546 | 948 | name = "pkcs8" |
|
| 547 | 949 | version = "0.11.0" |
|
| 548 | 950 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 4 unchanged lines |
||
| 553 | 955 | ] |
|
| 554 | 956 | ||
| 555 | 957 | [[package]] |
|
| 958 | + | name = "potential_utf" |
|
| 959 | + | version = "0.1.6" |
|
| 960 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 961 | + | checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" |
|
| 962 | + | dependencies = [ |
|
| 963 | + | "zerovec", |
|
| 964 | + | ] |
|
| 965 | + | ||
| 966 | + | [[package]] |
|
| 556 | 967 | name = "ppv-lite86" |
|
| 557 | 968 | version = "0.2.21" |
|
| 558 | 969 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 80 unchanged lines |
||
| 639 | 1050 | checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" |
|
| 640 | 1051 | ||
| 641 | 1052 | [[package]] |
|
| 1053 | + | name = "ring" |
|
| 1054 | + | version = "0.17.14" |
|
| 1055 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1056 | + | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" |
|
| 1057 | + | dependencies = [ |
|
| 1058 | + | "cc", |
|
| 1059 | + | "cfg-if", |
|
| 1060 | + | "getrandom 0.2.17", |
|
| 1061 | + | "libc", |
|
| 1062 | + | "untrusted", |
|
| 1063 | + | "windows-sys 0.52.0", |
|
| 1064 | + | ] |
|
| 1065 | + | ||
| 1066 | + | [[package]] |
|
| 642 | 1067 | name = "rustc_version" |
|
| 643 | 1068 | version = "0.4.1" |
|
| 644 | 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 12 unchanged lines |
||
| 657 | 1082 | "errno", |
|
| 658 | 1083 | "libc", |
|
| 659 | 1084 | "linux-raw-sys", |
|
| 660 | - | "windows-sys", |
|
| 1085 | + | "windows-sys 0.61.2", |
|
| 661 | 1086 | ] |
|
| 662 | 1087 | ||
| 663 | 1088 | [[package]] |
|
| 1089 | + | name = "rustls" |
|
| 1090 | + | version = "0.23.45" |
|
| 1091 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1092 | + | checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" |
|
| 1093 | + | dependencies = [ |
|
| 1094 | + | "log", |
|
| 1095 | + | "once_cell", |
|
| 1096 | + | "ring", |
|
| 1097 | + | "rustls-pki-types", |
|
| 1098 | + | "rustls-webpki", |
|
| 1099 | + | "subtle", |
|
| 1100 | + | "zeroize", |
|
| 1101 | + | ] |
|
| 1102 | + | ||
| 1103 | + | [[package]] |
|
| 1104 | + | name = "rustls-pki-types" |
|
| 1105 | + | version = "1.15.1" |
|
| 1106 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1107 | + | checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" |
|
| 1108 | + | dependencies = [ |
|
| 1109 | + | "zeroize", |
|
| 1110 | + | ] |
|
| 1111 | + | ||
| 1112 | + | [[package]] |
|
| 1113 | + | name = "rustls-webpki" |
|
| 1114 | + | version = "0.103.15" |
|
| 1115 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1116 | + | checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" |
|
| 1117 | + | dependencies = [ |
|
| 1118 | + | "ring", |
|
| 1119 | + | "rustls-pki-types", |
|
| 1120 | + | "untrusted", |
|
| 1121 | + | ] |
|
| 1122 | + | ||
| 1123 | + | [[package]] |
|
| 1124 | + | name = "ryu" |
|
| 1125 | + | version = "1.0.23" |
|
| 1126 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1127 | + | checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" |
|
| 1128 | + | ||
| 1129 | + | [[package]] |
|
| 664 | 1130 | name = "same-file" |
|
| 665 | 1131 | version = "1.0.6" |
|
| 666 | 1132 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 9 unchanged lines |
||
| 676 | 1142 | checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" |
|
| 677 | 1143 | ||
| 678 | 1144 | [[package]] |
|
| 1145 | + | name = "serde" |
|
| 1146 | + | version = "1.0.229" |
|
| 1147 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1148 | + | checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" |
|
| 1149 | + | dependencies = [ |
|
| 1150 | + | "serde_core", |
|
| 1151 | + | ] |
|
| 1152 | + | ||
| 1153 | + | [[package]] |
|
| 679 | 1154 | name = "serde_core" |
|
| 680 | 1155 | version = "1.0.229" |
|
| 681 | 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 14 unchanged lines |
||
| 696 | 1171 | ] |
|
| 697 | 1172 | ||
| 698 | 1173 | [[package]] |
|
| 1174 | + | name = "serde_json" |
|
| 1175 | + | version = "1.0.151" |
|
| 1176 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1177 | + | checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" |
|
| 1178 | + | dependencies = [ |
|
| 1179 | + | "itoa", |
|
| 1180 | + | "memchr", |
|
| 1181 | + | "serde", |
|
| 1182 | + | "serde_core", |
|
| 1183 | + | "zmij", |
|
| 1184 | + | ] |
|
| 1185 | + | ||
| 1186 | + | [[package]] |
|
| 1187 | + | name = "serde_path_to_error" |
|
| 1188 | + | version = "0.1.20" |
|
| 1189 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1190 | + | checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" |
|
| 1191 | + | dependencies = [ |
|
| 1192 | + | "itoa", |
|
| 1193 | + | "serde", |
|
| 1194 | + | "serde_core", |
|
| 1195 | + | ] |
|
| 1196 | + | ||
| 1197 | + | [[package]] |
|
| 1198 | + | name = "serde_urlencoded" |
|
| 1199 | + | version = "0.7.1" |
|
| 1200 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1201 | + | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" |
|
| 1202 | + | dependencies = [ |
|
| 1203 | + | "form_urlencoded", |
|
| 1204 | + | "itoa", |
|
| 1205 | + | "ryu", |
|
| 1206 | + | "serde", |
|
| 1207 | + | ] |
|
| 1208 | + | ||
| 1209 | + | [[package]] |
|
| 699 | 1210 | name = "sha2" |
|
| 700 | 1211 | version = "0.11.0" |
|
| 701 | 1212 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 32 unchanged lines |
||
| 734 | 1245 | ] |
|
| 735 | 1246 | ||
| 736 | 1247 | [[package]] |
|
| 1248 | + | name = "simd-adler32" |
|
| 1249 | + | version = "0.3.10" |
|
| 1250 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1251 | + | checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" |
|
| 1252 | + | ||
| 1253 | + | [[package]] |
|
| 737 | 1254 | name = "similar" |
|
| 738 | 1255 | version = "2.7.0" |
|
| 739 | 1256 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 740 | 1257 | checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" |
|
| 741 | 1258 | ||
| 742 | 1259 | [[package]] |
|
| 1260 | + | name = "slab" |
|
| 1261 | + | version = "0.4.12" |
|
| 1262 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1263 | + | checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" |
|
| 1264 | + | ||
| 1265 | + | [[package]] |
|
| 1266 | + | name = "smallvec" |
|
| 1267 | + | version = "1.16.2" |
|
| 1268 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1269 | + | checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" |
|
| 1270 | + | ||
| 1271 | + | [[package]] |
|
| 1272 | + | name = "socket2" |
|
| 1273 | + | version = "0.6.5" |
|
| 1274 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1275 | + | checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" |
|
| 1276 | + | dependencies = [ |
|
| 1277 | + | "libc", |
|
| 1278 | + | "windows-sys 0.61.2", |
|
| 1279 | + | ] |
|
| 1280 | + | ||
| 1281 | + | [[package]] |
|
| 743 | 1282 | name = "spki" |
|
| 744 | 1283 | version = "0.8.0" |
|
| 745 | 1284 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 10 unchanged lines |
||
| 756 | 1295 | checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" |
|
| 757 | 1296 | ||
| 758 | 1297 | [[package]] |
|
| 1298 | + | name = "stable_deref_trait" |
|
| 1299 | + | version = "1.2.1" |
|
| 1300 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1301 | + | checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" |
|
| 1302 | + | ||
| 1303 | + | [[package]] |
|
| 759 | 1304 | name = "strsim" |
|
| 760 | 1305 | version = "0.11.1" |
|
| 761 | 1306 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 28 unchanged lines |
||
| 790 | 1335 | ] |
|
| 791 | 1336 | ||
| 792 | 1337 | [[package]] |
|
| 1338 | + | name = "sync_wrapper" |
|
| 1339 | + | version = "1.0.2" |
|
| 1340 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1341 | + | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" |
|
| 1342 | + | ||
| 1343 | + | [[package]] |
|
| 1344 | + | name = "synstructure" |
|
| 1345 | + | version = "0.14.0" |
|
| 1346 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1347 | + | checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" |
|
| 1348 | + | dependencies = [ |
|
| 1349 | + | "proc-macro2", |
|
| 1350 | + | "quote", |
|
| 1351 | + | "syn 3.0.6", |
|
| 1352 | + | ] |
|
| 1353 | + | ||
| 1354 | + | [[package]] |
|
| 793 | 1355 | name = "tempfile" |
|
| 794 | 1356 | version = "3.27.0" |
|
| 795 | 1357 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 3 unchanged lines |
||
| 799 | 1361 | "getrandom 0.4.3", |
|
| 800 | 1362 | "once_cell", |
|
| 801 | 1363 | "rustix", |
|
| 802 | - | "windows-sys", |
|
| 1364 | + | "windows-sys 0.61.2", |
|
| 803 | 1365 | ] |
|
| 804 | 1366 | ||
| 805 | 1367 | [[package]] |
|
| … | 17 unchanged lines |
||
| 823 | 1385 | ] |
|
| 824 | 1386 | ||
| 825 | 1387 | [[package]] |
|
| 1388 | + | name = "tinystr" |
|
| 1389 | + | version = "0.8.4" |
|
| 1390 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1391 | + | checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" |
|
| 1392 | + | dependencies = [ |
|
| 1393 | + | "displaydoc", |
|
| 1394 | + | "zerovec", |
|
| 1395 | + | ] |
|
| 1396 | + | ||
| 1397 | + | [[package]] |
|
| 1398 | + | name = "tokio" |
|
| 1399 | + | version = "1.53.1" |
|
| 1400 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1401 | + | checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" |
|
| 1402 | + | dependencies = [ |
|
| 1403 | + | "libc", |
|
| 1404 | + | "mio", |
|
| 1405 | + | "pin-project-lite", |
|
| 1406 | + | "socket2", |
|
| 1407 | + | "tokio-macros", |
|
| 1408 | + | "windows-sys 0.61.2", |
|
| 1409 | + | ] |
|
| 1410 | + | ||
| 1411 | + | [[package]] |
|
| 1412 | + | name = "tokio-macros" |
|
| 1413 | + | version = "2.7.2" |
|
| 1414 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1415 | + | checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" |
|
| 1416 | + | dependencies = [ |
|
| 1417 | + | "proc-macro2", |
|
| 1418 | + | "quote", |
|
| 1419 | + | "syn 3.0.6", |
|
| 1420 | + | ] |
|
| 1421 | + | ||
| 1422 | + | [[package]] |
|
| 1423 | + | name = "tower" |
|
| 1424 | + | version = "0.5.3" |
|
| 1425 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1426 | + | checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" |
|
| 1427 | + | dependencies = [ |
|
| 1428 | + | "futures-core", |
|
| 1429 | + | "futures-util", |
|
| 1430 | + | "pin-project-lite", |
|
| 1431 | + | "sync_wrapper", |
|
| 1432 | + | "tokio", |
|
| 1433 | + | "tower-layer", |
|
| 1434 | + | "tower-service", |
|
| 1435 | + | "tracing", |
|
| 1436 | + | ] |
|
| 1437 | + | ||
| 1438 | + | [[package]] |
|
| 1439 | + | name = "tower-layer" |
|
| 1440 | + | version = "0.3.3" |
|
| 1441 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1442 | + | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" |
|
| 1443 | + | ||
| 1444 | + | [[package]] |
|
| 1445 | + | name = "tower-service" |
|
| 1446 | + | version = "0.3.3" |
|
| 1447 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1448 | + | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" |
|
| 1449 | + | ||
| 1450 | + | [[package]] |
|
| 1451 | + | name = "tracing" |
|
| 1452 | + | version = "0.1.44" |
|
| 1453 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1454 | + | checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" |
|
| 1455 | + | dependencies = [ |
|
| 1456 | + | "log", |
|
| 1457 | + | "pin-project-lite", |
|
| 1458 | + | "tracing-core", |
|
| 1459 | + | ] |
|
| 1460 | + | ||
| 1461 | + | [[package]] |
|
| 1462 | + | name = "tracing-core" |
|
| 1463 | + | version = "0.1.36" |
|
| 1464 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1465 | + | checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" |
|
| 1466 | + | dependencies = [ |
|
| 1467 | + | "once_cell", |
|
| 1468 | + | ] |
|
| 1469 | + | ||
| 1470 | + | [[package]] |
|
| 826 | 1471 | name = "typenum" |
|
| 827 | 1472 | version = "1.20.1" |
|
| 828 | 1473 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 6 unchanged lines |
||
| 835 | 1480 | checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" |
|
| 836 | 1481 | ||
| 837 | 1482 | [[package]] |
|
| 1483 | + | name = "untrusted" |
|
| 1484 | + | version = "0.9.0" |
|
| 1485 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1486 | + | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" |
|
| 1487 | + | ||
| 1488 | + | [[package]] |
|
| 1489 | + | name = "ureq" |
|
| 1490 | + | version = "2.12.1" |
|
| 1491 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1492 | + | checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" |
|
| 1493 | + | dependencies = [ |
|
| 1494 | + | "base64", |
|
| 1495 | + | "flate2", |
|
| 1496 | + | "log", |
|
| 1497 | + | "once_cell", |
|
| 1498 | + | "rustls", |
|
| 1499 | + | "rustls-pki-types", |
|
| 1500 | + | "url", |
|
| 1501 | + | "webpki-roots 0.26.11", |
|
| 1502 | + | ] |
|
| 1503 | + | ||
| 1504 | + | [[package]] |
|
| 1505 | + | name = "url" |
|
| 1506 | + | version = "2.5.8" |
|
| 1507 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1508 | + | checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" |
|
| 1509 | + | dependencies = [ |
|
| 1510 | + | "form_urlencoded", |
|
| 1511 | + | "idna", |
|
| 1512 | + | "percent-encoding", |
|
| 1513 | + | "serde", |
|
| 1514 | + | ] |
|
| 1515 | + | ||
| 1516 | + | [[package]] |
|
| 1517 | + | name = "utf8_iter" |
|
| 1518 | + | version = "1.0.4" |
|
| 1519 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1520 | + | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" |
|
| 1521 | + | ||
| 1522 | + | [[package]] |
|
| 838 | 1523 | name = "utf8parse" |
|
| 839 | 1524 | version = "0.2.2" |
|
| 840 | 1525 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 16 unchanged lines |
||
| 857 | 1542 | checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" |
|
| 858 | 1543 | ||
| 859 | 1544 | [[package]] |
|
| 1545 | + | name = "webpki-roots" |
|
| 1546 | + | version = "0.26.11" |
|
| 1547 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1548 | + | checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" |
|
| 1549 | + | dependencies = [ |
|
| 1550 | + | "webpki-roots 1.0.9", |
|
| 1551 | + | ] |
|
| 1552 | + | ||
| 1553 | + | [[package]] |
|
| 1554 | + | name = "webpki-roots" |
|
| 1555 | + | version = "1.0.9" |
|
| 1556 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1557 | + | checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" |
|
| 1558 | + | dependencies = [ |
|
| 1559 | + | "rustls-pki-types", |
|
| 1560 | + | ] |
|
| 1561 | + | ||
| 1562 | + | [[package]] |
|
| 860 | 1563 | name = "winapi-util" |
|
| 861 | 1564 | version = "0.1.11" |
|
| 862 | 1565 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 863 | 1566 | checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" |
|
| 864 | 1567 | dependencies = [ |
|
| 865 | - | "windows-sys", |
|
| 1568 | + | "windows-sys 0.61.2", |
|
| 866 | 1569 | ] |
|
| 867 | 1570 | ||
| 868 | 1571 | [[package]] |
|
| … | 4 unchanged lines |
||
| 873 | 1576 | ||
| 874 | 1577 | [[package]] |
|
| 875 | 1578 | name = "windows-sys" |
|
| 1579 | + | version = "0.52.0" |
|
| 1580 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1581 | + | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" |
|
| 1582 | + | dependencies = [ |
|
| 1583 | + | "windows-targets", |
|
| 1584 | + | ] |
|
| 1585 | + | ||
| 1586 | + | [[package]] |
|
| 1587 | + | name = "windows-sys" |
|
| 876 | 1588 | version = "0.61.2" |
|
| 877 | 1589 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 878 | 1590 | checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" |
|
| … | 2 unchanged lines |
||
| 881 | 1593 | ] |
|
| 882 | 1594 | ||
| 883 | 1595 | [[package]] |
|
| 1596 | + | name = "windows-targets" |
|
| 1597 | + | version = "0.52.6" |
|
| 1598 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1599 | + | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" |
|
| 1600 | + | dependencies = [ |
|
| 1601 | + | "windows_aarch64_gnullvm", |
|
| 1602 | + | "windows_aarch64_msvc", |
|
| 1603 | + | "windows_i686_gnu", |
|
| 1604 | + | "windows_i686_gnullvm", |
|
| 1605 | + | "windows_i686_msvc", |
|
| 1606 | + | "windows_x86_64_gnu", |
|
| 1607 | + | "windows_x86_64_gnullvm", |
|
| 1608 | + | "windows_x86_64_msvc", |
|
| 1609 | + | ] |
|
| 1610 | + | ||
| 1611 | + | [[package]] |
|
| 1612 | + | name = "windows_aarch64_gnullvm" |
|
| 1613 | + | version = "0.52.6" |
|
| 1614 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1615 | + | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" |
|
| 1616 | + | ||
| 1617 | + | [[package]] |
|
| 1618 | + | name = "windows_aarch64_msvc" |
|
| 1619 | + | version = "0.52.6" |
|
| 1620 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1621 | + | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" |
|
| 1622 | + | ||
| 1623 | + | [[package]] |
|
| 1624 | + | name = "windows_i686_gnu" |
|
| 1625 | + | version = "0.52.6" |
|
| 1626 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1627 | + | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" |
|
| 1628 | + | ||
| 1629 | + | [[package]] |
|
| 1630 | + | name = "windows_i686_gnullvm" |
|
| 1631 | + | version = "0.52.6" |
|
| 1632 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1633 | + | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" |
|
| 1634 | + | ||
| 1635 | + | [[package]] |
|
| 1636 | + | name = "windows_i686_msvc" |
|
| 1637 | + | version = "0.52.6" |
|
| 1638 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1639 | + | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" |
|
| 1640 | + | ||
| 1641 | + | [[package]] |
|
| 1642 | + | name = "windows_x86_64_gnu" |
|
| 1643 | + | version = "0.52.6" |
|
| 1644 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1645 | + | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" |
|
| 1646 | + | ||
| 1647 | + | [[package]] |
|
| 1648 | + | name = "windows_x86_64_gnullvm" |
|
| 1649 | + | version = "0.52.6" |
|
| 1650 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1651 | + | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" |
|
| 1652 | + | ||
| 1653 | + | [[package]] |
|
| 1654 | + | name = "windows_x86_64_msvc" |
|
| 1655 | + | version = "0.52.6" |
|
| 1656 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1657 | + | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" |
|
| 1658 | + | ||
| 1659 | + | [[package]] |
|
| 1660 | + | name = "writeable" |
|
| 1661 | + | version = "0.6.4" |
|
| 1662 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1663 | + | checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" |
|
| 1664 | + | ||
| 1665 | + | [[package]] |
|
| 1666 | + | name = "yoke" |
|
| 1667 | + | version = "0.8.3" |
|
| 1668 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1669 | + | checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" |
|
| 1670 | + | dependencies = [ |
|
| 1671 | + | "stable_deref_trait", |
|
| 1672 | + | "yoke-derive", |
|
| 1673 | + | "zerofrom", |
|
| 1674 | + | ] |
|
| 1675 | + | ||
| 1676 | + | [[package]] |
|
| 1677 | + | name = "yoke-derive" |
|
| 1678 | + | version = "0.8.3" |
|
| 1679 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1680 | + | checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78" |
|
| 1681 | + | dependencies = [ |
|
| 1682 | + | "proc-macro2", |
|
| 1683 | + | "quote", |
|
| 1684 | + | "syn 3.0.6", |
|
| 1685 | + | "synstructure", |
|
| 1686 | + | ] |
|
| 1687 | + | ||
| 1688 | + | [[package]] |
|
| 884 | 1689 | name = "zerocopy" |
|
| 885 | 1690 | version = "0.8.59" |
|
| 886 | 1691 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| … | 14 unchanged lines |
||
| 901 | 1706 | ] |
|
| 902 | 1707 | ||
| 903 | 1708 | [[package]] |
|
| 1709 | + | name = "zerofrom" |
|
| 1710 | + | version = "0.1.8" |
|
| 1711 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1712 | + | checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" |
|
| 1713 | + | dependencies = [ |
|
| 1714 | + | "zerofrom-derive", |
|
| 1715 | + | ] |
|
| 1716 | + | ||
| 1717 | + | [[package]] |
|
| 1718 | + | name = "zerofrom-derive" |
|
| 1719 | + | version = "0.1.8" |
|
| 1720 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1721 | + | checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" |
|
| 1722 | + | dependencies = [ |
|
| 1723 | + | "proc-macro2", |
|
| 1724 | + | "quote", |
|
| 1725 | + | "syn 3.0.6", |
|
| 1726 | + | "synstructure", |
|
| 1727 | + | ] |
|
| 1728 | + | ||
| 1729 | + | [[package]] |
|
| 904 | 1730 | name = "zeroize" |
|
| 905 | 1731 | version = "1.9.0" |
|
| 906 | 1732 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 907 | 1733 | checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" |
|
| 1734 | + | ||
| 1735 | + | [[package]] |
|
| 1736 | + | name = "zerotrie" |
|
| 1737 | + | version = "0.2.5" |
|
| 1738 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1739 | + | checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" |
|
| 1740 | + | dependencies = [ |
|
| 1741 | + | "displaydoc", |
|
| 1742 | + | "yoke", |
|
| 1743 | + | "zerofrom", |
|
| 1744 | + | ] |
|
| 1745 | + | ||
| 1746 | + | [[package]] |
|
| 1747 | + | name = "zerovec" |
|
| 1748 | + | version = "0.11.8" |
|
| 1749 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1750 | + | checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" |
|
| 1751 | + | dependencies = [ |
|
| 1752 | + | "yoke", |
|
| 1753 | + | "zerofrom", |
|
| 1754 | + | "zerovec-derive", |
|
| 1755 | + | ] |
|
| 1756 | + | ||
| 1757 | + | [[package]] |
|
| 1758 | + | name = "zerovec-derive" |
|
| 1759 | + | version = "0.11.6" |
|
| 1760 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1761 | + | checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" |
|
| 1762 | + | dependencies = [ |
|
| 1763 | + | "proc-macro2", |
|
| 1764 | + | "quote", |
|
| 1765 | + | "syn 3.0.6", |
|
| 1766 | + | ] |
|
| 1767 | + | ||
| 1768 | + | [[package]] |
|
| 1769 | + | name = "zlib-rs" |
|
| 1770 | + | version = "0.6.8" |
|
| 1771 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1772 | + | checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" |
|
| 1773 | + | ||
| 1774 | + | [[package]] |
|
| 1775 | + | name = "zmij" |
|
| 1776 | + | version = "1.0.23" |
|
| 1777 | + | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1778 | + | checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" |
modified Cargo.toml +2 −1
| 1 | 1 | [workspace] |
|
| 2 | - | members = ["crates/oxy-core", "."] |
|
| 2 | + | members = ["crates/oxy-core", "crates/oxy-server", "."] |
|
| 3 | 3 | resolver = "2" |
|
| 4 | 4 | ||
| 5 | 5 | [workspace.package] |
|
| … | 15 unchanged lines |
||
| 21 | 21 | ||
| 22 | 22 | [dependencies] |
|
| 23 | 23 | oxy-core = { path = "crates/oxy-core" } |
|
| 24 | + | oxy-server = { path = "crates/oxy-server" } |
|
| 24 | 25 | clap = { version = "4", features = ["derive"] } |
|
| 25 | 26 | anyhow = "1" |
|
| 26 | 27 | libc = "0.2" |
|
| … | 3 unchanged lines |
modified crates/oxy-core/Cargo.toml +1 −0
| … | 13 unchanged lines |
||
| 14 | 14 | ignore = "0.4" |
|
| 15 | 15 | ml-dsa = "0.1" |
|
| 16 | 16 | ed25519-dalek = "3" |
|
| 17 | + | ureq = "2" |
|
| 17 | 18 | ||
| 18 | 19 | [dev-dependencies] |
|
| 19 | 20 | tempfile = "3" |
modified crates/oxy-core/src/protocol.rs +360 −6
| 1 | 1 | //! Transport-agnostic protocol (spec §16): what a client asks of a remote and |
|
| 2 | 2 | //! what a server does with a push. `LocalRemote` serves another repository |
|
| 3 | - | //! directory in-process; an HTTPS transport implements the same trait and the |
|
| 4 | - | //! server side reuses `Repo::receive_push` and friends unchanged. |
|
| 3 | + | //! directory in-process, `HttpRemote` speaks to an `oxy-server` over HTTP(S), |
|
| 4 | + | //! and the server side reuses `Repo::receive_push` and friends unchanged. |
|
| 5 | + | //! |
|
| 6 | + | //! Wire messages are deterministic CBOR (`wire` module); over HTTP each is a |
|
| 7 | + | //! request or response body with content type `application/oxy-cbor`. |
|
| 5 | 8 | ||
| 6 | 9 | use crate::attest::Attest; |
|
| 10 | + | use crate::cbor::{self, Value}; |
|
| 7 | 11 | use crate::hash::{ChangeHash, ObjectRef, ObjectType}; |
|
| 8 | 12 | use crate::identity::Signature; |
|
| 9 | - | use crate::objects::Change; |
|
| 13 | + | use crate::objects::{self as o, Change, ObjectError}; |
|
| 10 | 14 | use crate::repo::{Repo, RepoError, Result}; |
|
| 11 | 15 | use std::path::Path; |
|
| 12 | 16 | ||
| 17 | + | pub const CONTENT_TYPE: &str = "application/oxy-cbor"; |
|
| 18 | + | pub const PROTOCOL_VERSION: u64 = 1; |
|
| 19 | + | ||
| 13 | 20 | /// An attestation together with the signatures over its ObjectRef. |
|
| 14 | 21 | #[derive(Clone, Debug, PartialEq, Eq)] |
|
| 15 | 22 | pub struct SignedAttest { |
|
| … | 53 unchanged lines |
||
| 69 | 76 | fn push(&self, bundle: Bundle, updates: Vec<RefUpdate>) -> Result<Vec<RefResult>>; |
|
| 70 | 77 | } |
|
| 71 | 78 | ||
| 79 | + | // ---------------------------------------------------------------- local |
|
| 80 | + | ||
| 72 | 81 | /// Another repository on this machine, served in-process. |
|
| 73 | 82 | pub struct LocalRemote { |
|
| 74 | 83 | pub repo: Repo, |
|
| … | 23 unchanged lines |
||
| 98 | 107 | } |
|
| 99 | 108 | } |
|
| 100 | 109 | ||
| 101 | - | /// Open a remote by URL. Only local paths (optionally `file://`) exist so far. |
|
| 102 | - | pub fn open_remote(url: &str) -> Result<Box<dyn Remote>> { |
|
| 110 | + | // ---------------------------------------------------------------- http |
|
| 111 | + | ||
| 112 | + | /// A remote served by `oxy-server` at `<base>/oxy/v1/...`. |
|
| 113 | + | pub struct HttpRemote { |
|
| 114 | + | base: String, |
|
| 115 | + | token: Option<String>, |
|
| 116 | + | agent: ureq::Agent, |
|
| 117 | + | } |
|
| 118 | + | ||
| 119 | + | impl HttpRemote { |
|
| 120 | + | pub fn new(base: &str, token: Option<String>) -> HttpRemote { |
|
| 121 | + | HttpRemote { base: base.trim_end_matches('/').to_string(), token, agent: ureq::Agent::new() } |
|
| 122 | + | } |
|
| 123 | + | ||
| 124 | + | fn call(&self, post: bool, endpoint: &str, body: Option<&[u8]>) -> Result<Vec<u8>> { |
|
| 125 | + | let url = format!("{}/oxy/v1/{endpoint}", self.base); |
|
| 126 | + | let mut req = if post { self.agent.post(&url) } else { self.agent.get(&url) }; |
|
| 127 | + | req = req.set("Accept", CONTENT_TYPE); |
|
| 128 | + | if let Some(t) = &self.token { |
|
| 129 | + | req = req.set("Authorization", &format!("Bearer {t}")); |
|
| 130 | + | } |
|
| 131 | + | let resp = match body { |
|
| 132 | + | Some(b) => req.set("Content-Type", CONTENT_TYPE).send_bytes(b), |
|
| 133 | + | None => req.call(), |
|
| 134 | + | }; |
|
| 135 | + | match resp { |
|
| 136 | + | Ok(r) => { |
|
| 137 | + | let mut out = Vec::new(); |
|
| 138 | + | r.into_reader().read_to_end(&mut out).map_err(|e| RepoError::Msg(format!("{url}: {e}")))?; |
|
| 139 | + | Ok(out) |
|
| 140 | + | } |
|
| 141 | + | Err(ureq::Error::Status(code, r)) => { |
|
| 142 | + | let text = r.into_string().unwrap_or_default(); |
|
| 143 | + | Err(RepoError::Msg(format!("{url}: HTTP {code}: {}", text.trim()))) |
|
| 144 | + | } |
|
| 145 | + | Err(e) => Err(RepoError::Msg(format!("{url}: {e}"))), |
|
| 146 | + | } |
|
| 147 | + | } |
|
| 148 | + | } |
|
| 149 | + | ||
| 150 | + | impl Remote for HttpRemote { |
|
| 151 | + | fn repo_id(&self) -> Result<[u8; 16]> { |
|
| 152 | + | wire::decode_capabilities(&self.call(false, "capabilities", None)?) |
|
| 153 | + | } |
|
| 154 | + | fn refs(&self) -> Result<Vec<RemoteRef>> { |
|
| 155 | + | Ok(wire::decode_refs(&self.call(false, "refs", None)?)?.1) |
|
| 156 | + | } |
|
| 157 | + | fn negotiate(&self, have: &[ChangeHash], want: &[ChangeHash]) -> Result<Vec<ChangeHash>> { |
|
| 158 | + | wire::decode_hashes(&self.call(true, "negotiate", Some(&wire::encode_negotiate(have, want)))?) |
|
| 159 | + | } |
|
| 160 | + | fn fetch(&self, changes: &[ChangeHash]) -> Result<Bundle> { |
|
| 161 | + | wire::decode_bundle(&self.call(true, "fetch", Some(&wire::encode_hashes(changes)))?) |
|
| 162 | + | } |
|
| 163 | + | fn push(&self, bundle: Bundle, updates: Vec<RefUpdate>) -> Result<Vec<RefResult>> { |
|
| 164 | + | wire::decode_push_response(&self.call(true, "push", Some(&wire::encode_push(&bundle, &updates)))?) |
|
| 165 | + | } |
|
| 166 | + | } |
|
| 167 | + | ||
| 168 | + | /// Open a remote by URL: a local path (optionally `file://`), or `http(s)://`. |
|
| 169 | + | pub fn open_remote(url: &str, token: Option<String>) -> Result<Box<dyn Remote>> { |
|
| 170 | + | if url.starts_with("http://") || url.starts_with("https://") { |
|
| 171 | + | return Ok(Box::new(HttpRemote::new(url, token))); |
|
| 172 | + | } |
|
| 103 | 173 | let path = url.strip_prefix("file://").unwrap_or(url); |
|
| 104 | 174 | if path.contains("://") { |
|
| 105 | - | return Err(RepoError::Msg(format!("unsupported remote URL {url:?}: only local paths are implemented"))); |
|
| 175 | + | return Err(RepoError::Msg(format!("unsupported remote URL {url:?}"))); |
|
| 106 | 176 | } |
|
| 107 | 177 | Ok(Box::new(LocalRemote::open(Path::new(path))?)) |
|
| 108 | 178 | } |
|
| 179 | + | ||
| 180 | + | // ---------------------------------------------------------------- wire |
|
| 181 | + | ||
| 182 | + | /// CBOR encodings of the protocol messages (spec §16.1). |
|
| 183 | + | pub mod wire { |
|
| 184 | + | use super::*; |
|
| 185 | + | ||
| 186 | + | fn schema(msg: &str) -> RepoError { |
|
| 187 | + | RepoError::Object(ObjectError::Schema(msg.into())) |
|
| 188 | + | } |
|
| 189 | + | fn sigs_value(sigs: &[Signature]) -> Value { |
|
| 190 | + | Value::Array(sigs.iter().map(|s| s.to_value()).collect()) |
|
| 191 | + | } |
|
| 192 | + | fn sigs_from(v: &Value) -> Result<Vec<Signature>> { |
|
| 193 | + | Ok(o::array(v, "signatures")?.iter().map(Signature::from_value).collect::<std::result::Result<Vec<_>, _>>()?) |
|
| 194 | + | } |
|
| 195 | + | fn hashes_value(hs: &[ObjectRef]) -> Value { |
|
| 196 | + | Value::Array(hs.iter().map(|h| Value::Bytes(h.0.to_vec())).collect()) |
|
| 197 | + | } |
|
| 198 | + | fn hashes_from(v: &Value) -> Result<Vec<ObjectRef>> { |
|
| 199 | + | Ok(o::array(v, "hashes")?.iter().map(|h| o::oref(h, "hash")).collect::<std::result::Result<Vec<_>, _>>()?) |
|
| 200 | + | } |
|
| 201 | + | fn attest_value(sa: &SignedAttest) -> Value { |
|
| 202 | + | Value::Array(vec![Value::Bytes(sa.attest.encode()), sigs_value(&sa.sigs)]) |
|
| 203 | + | } |
|
| 204 | + | fn attest_from(v: &Value) -> Result<SignedAttest> { |
|
| 205 | + | let a = o::array(v, "signed attest")?; |
|
| 206 | + | if a.len() != 2 { |
|
| 207 | + | return Err(schema("signed attest: expected [attest, sigs]")); |
|
| 208 | + | } |
|
| 209 | + | Ok(SignedAttest { attest: Attest::decode(o::bytes(&a[0], "attest")?)?, sigs: sigs_from(&a[1])? }) |
|
| 210 | + | } |
|
| 211 | + | fn opt_attest_value(sa: &Option<SignedAttest>) -> Value { |
|
| 212 | + | sa.as_ref().map(attest_value).unwrap_or(Value::Null) |
|
| 213 | + | } |
|
| 214 | + | fn opt_attest_from(v: &Value) -> Result<Option<SignedAttest>> { |
|
| 215 | + | if v.is_null() { Ok(None) } else { attest_from(v).map(Some) } |
|
| 216 | + | } |
|
| 217 | + | fn map<'a>(v: &'a Value, what: &str) -> Result<&'a o::Map> { |
|
| 218 | + | Ok(o::map(v, what)?) |
|
| 219 | + | } |
|
| 220 | + | fn field<'a>(m: &'a o::Map, k: u64, what: &str) -> Result<&'a Value> { |
|
| 221 | + | Ok(o::field(m, k, what)?) |
|
| 222 | + | } |
|
| 223 | + | ||
| 224 | + | pub fn bundle_value(b: &Bundle) -> Value { |
|
| 225 | + | Value::map([ |
|
| 226 | + | ( |
|
| 227 | + | 0, |
|
| 228 | + | Value::Array(b.changes.iter().map(|(c, s)| Value::Array(vec![Value::Bytes(c.encode()), sigs_value(s)])).collect()), |
|
| 229 | + | ), |
|
| 230 | + | (1, Value::Array(b.attests.iter().map(attest_value).collect())), |
|
| 231 | + | ( |
|
| 232 | + | 2, |
|
| 233 | + | Value::Array( |
|
| 234 | + | b.objects.iter().map(|(t, body)| Value::Array(vec![Value::Text(t.name().into()), Value::Bytes(body.clone())])).collect(), |
|
| 235 | + | ), |
|
| 236 | + | ), |
|
| 237 | + | ]) |
|
| 238 | + | } |
|
| 239 | + | ||
| 240 | + | pub fn bundle_from_value(v: &Value) -> Result<Bundle> { |
|
| 241 | + | let m = map(v, "bundle")?; |
|
| 242 | + | let mut b = Bundle::default(); |
|
| 243 | + | for c in o::array(field(m, 0, "changes")?, "changes")? { |
|
| 244 | + | let pair = o::array(c, "change entry")?; |
|
| 245 | + | if pair.len() != 2 { |
|
| 246 | + | return Err(schema("change entry: expected [change, sigs]")); |
|
| 247 | + | } |
|
| 248 | + | b.changes.push((Change::decode(o::bytes(&pair[0], "change")?)?, sigs_from(&pair[1])?)); |
|
| 249 | + | } |
|
| 250 | + | for a in o::array(field(m, 1, "attests")?, "attests")? { |
|
| 251 | + | b.attests.push(attest_from(a)?); |
|
| 252 | + | } |
|
| 253 | + | for ob in o::array(field(m, 2, "objects")?, "objects")? { |
|
| 254 | + | let pair = o::array(ob, "object")?; |
|
| 255 | + | if pair.len() != 2 { |
|
| 256 | + | return Err(schema("object: expected [type, body]")); |
|
| 257 | + | } |
|
| 258 | + | let ty = ObjectType::from_name(o::text(&pair[0], "object type")?).ok_or_else(|| schema("unknown object type"))?; |
|
| 259 | + | b.objects.push((ty, o::bytes(&pair[1], "object body")?.to_vec())); |
|
| 260 | + | } |
|
| 261 | + | Ok(b) |
|
| 262 | + | } |
|
| 263 | + | ||
| 264 | + | pub fn encode_bundle(b: &Bundle) -> Vec<u8> { |
|
| 265 | + | cbor::encode(&bundle_value(b)) |
|
| 266 | + | } |
|
| 267 | + | pub fn decode_bundle(bytes: &[u8]) -> Result<Bundle> { |
|
| 268 | + | bundle_from_value(&cbor::decode(bytes).map_err(ObjectError::from)?) |
|
| 269 | + | } |
|
| 270 | + | ||
| 271 | + | pub fn encode_capabilities(id: [u8; 16]) -> Vec<u8> { |
|
| 272 | + | cbor::encode(&Value::map([ |
|
| 273 | + | (0, Value::Array(vec![Value::Uint(PROTOCOL_VERSION)])), |
|
| 274 | + | (1, Value::Bytes(id.to_vec())), |
|
| 275 | + | (2, Value::Array(vec![Value::Text("seal".into())])), |
|
| 276 | + | ])) |
|
| 277 | + | } |
|
| 278 | + | pub fn decode_capabilities(bytes: &[u8]) -> Result<[u8; 16]> { |
|
| 279 | + | let v = cbor::decode(bytes).map_err(ObjectError::from)?; |
|
| 280 | + | let m = map(&v, "capabilities")?; |
|
| 281 | + | let versions = o::array(field(m, 0, "versions")?, "versions")?; |
|
| 282 | + | if !versions.iter().any(|x| x.as_uint() == Some(PROTOCOL_VERSION)) { |
|
| 283 | + | return Err(RepoError::Msg("remote does not speak protocol version 1".into())); |
|
| 284 | + | } |
|
| 285 | + | o::bytes(field(m, 1, "repo id")?, "repo id")?.try_into().map_err(|_| schema("repo id: expected 16 bytes")) |
|
| 286 | + | } |
|
| 287 | + | ||
| 288 | + | pub fn encode_refs(id: [u8; 16], refs: &[RemoteRef]) -> Vec<u8> { |
|
| 289 | + | cbor::encode(&Value::map([ |
|
| 290 | + | ( |
|
| 291 | + | 0, |
|
| 292 | + | Value::Array( |
|
| 293 | + | refs.iter() |
|
| 294 | + | .map(|r| { |
|
| 295 | + | Value::map([ |
|
| 296 | + | (0, Value::Text(r.name.clone())), |
|
| 297 | + | (1, Value::Bytes(r.state.0.to_vec())), |
|
| 298 | + | (2, hashes_value(&r.heads)), |
|
| 299 | + | (3, opt_attest_value(&r.attestation)), |
|
| 300 | + | (4, r.last_serial.map(Value::Uint).unwrap_or(Value::Null)), |
|
| 301 | + | ]) |
|
| 302 | + | }) |
|
| 303 | + | .collect(), |
|
| 304 | + | ), |
|
| 305 | + | ), |
|
| 306 | + | (1, Value::Bytes(id.to_vec())), |
|
| 307 | + | ])) |
|
| 308 | + | } |
|
| 309 | + | pub fn decode_refs(bytes: &[u8]) -> Result<([u8; 16], Vec<RemoteRef>)> { |
|
| 310 | + | let v = cbor::decode(bytes).map_err(ObjectError::from)?; |
|
| 311 | + | let m = map(&v, "refs")?; |
|
| 312 | + | let id: [u8; 16] = o::bytes(field(m, 1, "repo id")?, "repo id")?.try_into().map_err(|_| schema("repo id: expected 16 bytes"))?; |
|
| 313 | + | let mut refs = Vec::new(); |
|
| 314 | + | for r in o::array(field(m, 0, "refs")?, "refs")? { |
|
| 315 | + | let rm = map(r, "ref")?; |
|
| 316 | + | let ls = field(rm, 4, "last serial")?; |
|
| 317 | + | refs.push(RemoteRef { |
|
| 318 | + | name: o::text(field(rm, 0, "name")?, "name")?.to_owned(), |
|
| 319 | + | state: o::oref(field(rm, 1, "state")?, "state")?, |
|
| 320 | + | heads: hashes_from(field(rm, 2, "heads")?)?, |
|
| 321 | + | attestation: opt_attest_from(field(rm, 3, "attestation")?)?, |
|
| 322 | + | last_serial: if ls.is_null() { None } else { Some(o::uint(ls, "last serial")?) }, |
|
| 323 | + | }); |
|
| 324 | + | } |
|
| 325 | + | Ok((id, refs)) |
|
| 326 | + | } |
|
| 327 | + | ||
| 328 | + | pub fn encode_negotiate(have: &[ChangeHash], want: &[ChangeHash]) -> Vec<u8> { |
|
| 329 | + | cbor::encode(&Value::map([(0, hashes_value(have)), (1, hashes_value(want))])) |
|
| 330 | + | } |
|
| 331 | + | pub fn decode_negotiate(bytes: &[u8]) -> Result<(Vec<ChangeHash>, Vec<ChangeHash>)> { |
|
| 332 | + | let v = cbor::decode(bytes).map_err(ObjectError::from)?; |
|
| 333 | + | let m = map(&v, "negotiate")?; |
|
| 334 | + | Ok((hashes_from(field(m, 0, "have")?)?, hashes_from(field(m, 1, "want")?)?)) |
|
| 335 | + | } |
|
| 336 | + | ||
| 337 | + | pub fn encode_hashes(hs: &[ChangeHash]) -> Vec<u8> { |
|
| 338 | + | cbor::encode(&Value::map([(0, hashes_value(hs))])) |
|
| 339 | + | } |
|
| 340 | + | pub fn decode_hashes(bytes: &[u8]) -> Result<Vec<ChangeHash>> { |
|
| 341 | + | let v = cbor::decode(bytes).map_err(ObjectError::from)?; |
|
| 342 | + | hashes_from(field(map(&v, "hashes")?, 0, "hashes")?) |
|
| 343 | + | } |
|
| 344 | + | ||
| 345 | + | pub fn encode_push(bundle: &Bundle, updates: &[RefUpdate]) -> Vec<u8> { |
|
| 346 | + | cbor::encode(&Value::map([ |
|
| 347 | + | (0, bundle_value(bundle)), |
|
| 348 | + | ( |
|
| 349 | + | 1, |
|
| 350 | + | Value::Array( |
|
| 351 | + | updates |
|
| 352 | + | .iter() |
|
| 353 | + | .map(|u| { |
|
| 354 | + | Value::map([ |
|
| 355 | + | (0, Value::Text(u.name.clone())), |
|
| 356 | + | (1, Value::Bytes(u.old_state.0.to_vec())), |
|
| 357 | + | (2, Value::Bytes(u.new_state.0.to_vec())), |
|
| 358 | + | (3, Value::Bool(u.force)), |
|
| 359 | + | (4, opt_attest_value(&u.attestation)), |
|
| 360 | + | ]) |
|
| 361 | + | }) |
|
| 362 | + | .collect(), |
|
| 363 | + | ), |
|
| 364 | + | ), |
|
| 365 | + | ])) |
|
| 366 | + | } |
|
| 367 | + | pub fn decode_push(bytes: &[u8]) -> Result<(Bundle, Vec<RefUpdate>)> { |
|
| 368 | + | let v = cbor::decode(bytes).map_err(ObjectError::from)?; |
|
| 369 | + | let m = map(&v, "push")?; |
|
| 370 | + | let bundle = bundle_from_value(field(m, 0, "bundle")?)?; |
|
| 371 | + | let mut updates = Vec::new(); |
|
| 372 | + | for u in o::array(field(m, 1, "updates")?, "updates")? { |
|
| 373 | + | let um = map(u, "update")?; |
|
| 374 | + | updates.push(RefUpdate { |
|
| 375 | + | name: o::text(field(um, 0, "name")?, "name")?.to_owned(), |
|
| 376 | + | old_state: o::oref(field(um, 1, "old_state")?, "old_state")?, |
|
| 377 | + | new_state: o::oref(field(um, 2, "new_state")?, "new_state")?, |
|
| 378 | + | force: field(um, 3, "force")?.as_bool().ok_or_else(|| schema("force: expected bool"))?, |
|
| 379 | + | attestation: opt_attest_from(field(um, 4, "attestation")?)?, |
|
| 380 | + | }); |
|
| 381 | + | } |
|
| 382 | + | Ok((bundle, updates)) |
|
| 383 | + | } |
|
| 384 | + | ||
| 385 | + | pub fn encode_push_response(results: &[RefResult]) -> Vec<u8> { |
|
| 386 | + | cbor::encode(&Value::map([( |
|
| 387 | + | 0, |
|
| 388 | + | Value::Array( |
|
| 389 | + | results |
|
| 390 | + | .iter() |
|
| 391 | + | .map(|r| Value::map([(0, Value::Text(r.name.clone())), (1, Value::Bool(r.ok)), (2, Value::Text(r.reason.clone()))])) |
|
| 392 | + | .collect(), |
|
| 393 | + | ), |
|
| 394 | + | )])) |
|
| 395 | + | } |
|
| 396 | + | pub fn decode_push_response(bytes: &[u8]) -> Result<Vec<RefResult>> { |
|
| 397 | + | let v = cbor::decode(bytes).map_err(ObjectError::from)?; |
|
| 398 | + | let m = map(&v, "push response")?; |
|
| 399 | + | let mut out = Vec::new(); |
|
| 400 | + | for r in o::array(field(m, 0, "results")?, "results")? { |
|
| 401 | + | let rm = map(r, "result")?; |
|
| 402 | + | out.push(RefResult { |
|
| 403 | + | name: o::text(field(rm, 0, "name")?, "name")?.to_owned(), |
|
| 404 | + | ok: field(rm, 1, "ok")?.as_bool().ok_or_else(|| schema("ok: expected bool"))?, |
|
| 405 | + | reason: o::text(field(rm, 2, "reason")?, "reason")?.to_owned(), |
|
| 406 | + | }); |
|
| 407 | + | } |
|
| 408 | + | Ok(out) |
|
| 409 | + | } |
|
| 410 | + | } |
|
| 411 | + | ||
| 412 | + | #[cfg(test)] |
|
| 413 | + | mod tests { |
|
| 414 | + | use super::*; |
|
| 415 | + | use crate::attest::AttestBody; |
|
| 416 | + | use crate::objects::Hlc; |
|
| 417 | + | ||
| 418 | + | #[test] |
|
| 419 | + | fn wire_round_trips() { |
|
| 420 | + | let c = crate::objects::tests::sample_change(); |
|
| 421 | + | let sk = crate::identity::SecretKey { ed_seed: [1; 32], ml_seed: [2; 32] }; |
|
| 422 | + | let id = sk.identity("a", "b", 0); |
|
| 423 | + | let key = id.hash(); |
|
| 424 | + | let sig = sk.sign(crate::identity::Domain::Change, &key, &c.hash()); |
|
| 425 | + | let seal = Attest::seal([3; 16], vec![c.hash()]); |
|
| 426 | + | let seal_sig = sk.sign(crate::identity::Domain::Attest, &key, &seal.hash()); |
|
| 427 | + | let bundle = Bundle { |
|
| 428 | + | changes: vec![(c.clone(), vec![sig.clone()])], |
|
| 429 | + | attests: vec![SignedAttest { attest: seal.clone(), sigs: vec![seal_sig] }], |
|
| 430 | + | objects: vec![(ObjectType::Identity, id.encode()), (ObjectType::Chunk, b"xyz".to_vec())], |
|
| 431 | + | }; |
|
| 432 | + | let d = wire::decode_bundle(&wire::encode_bundle(&bundle)).unwrap(); |
|
| 433 | + | assert_eq!(d.changes, bundle.changes); |
|
| 434 | + | assert_eq!(d.attests, bundle.attests); |
|
| 435 | + | assert_eq!(d.objects, bundle.objects); |
|
| 436 | + | ||
| 437 | + | let att = Attest { repo: [3; 16], hlc: Hlc::default(), body: AttestBody::Ref { name: "main".into(), old_state: ObjectRef([1; 32]), new_state: ObjectRef([2; 32]), serial: 4 } }; |
|
| 438 | + | let refs = vec![ |
|
| 439 | + | RemoteRef { name: "main".into(), state: ObjectRef([2; 32]), heads: vec![c.hash()], attestation: Some(SignedAttest { attest: att.clone(), sigs: vec![sig.clone()] }), last_serial: Some(4) }, |
|
| 440 | + | RemoteRef { name: "dev".into(), state: ObjectRef([5; 32]), heads: vec![], attestation: None, last_serial: None }, |
|
| 441 | + | ]; |
|
| 442 | + | let (id_back, back) = wire::decode_refs(&wire::encode_refs([3; 16], &refs)).unwrap(); |
|
| 443 | + | assert_eq!(id_back, [3; 16]); |
|
| 444 | + | assert_eq!(back.len(), 2); |
|
| 445 | + | assert_eq!(back[0].attestation.as_ref().unwrap().attest, att); |
|
| 446 | + | assert_eq!(back[1].last_serial, None); |
|
| 447 | + | ||
| 448 | + | let (h, w) = wire::decode_negotiate(&wire::encode_negotiate(&[ObjectRef([1; 32])], &[ObjectRef([2; 32])])).unwrap(); |
|
| 449 | + | assert_eq!((h, w), (vec![ObjectRef([1; 32])], vec![ObjectRef([2; 32])])); |
|
| 450 | + | assert_eq!(wire::decode_hashes(&wire::encode_hashes(&[c.hash()])).unwrap(), vec![c.hash()]); |
|
| 451 | + | ||
| 452 | + | let up = RefUpdate { name: "main".into(), old_state: ObjectRef([1; 32]), new_state: ObjectRef([2; 32]), force: true, attestation: Some(SignedAttest { attest: att, sigs: vec![sig] }) }; |
|
| 453 | + | let (b2, ups) = wire::decode_push(&wire::encode_push(&bundle, std::slice::from_ref(&up))).unwrap(); |
|
| 454 | + | assert_eq!(b2.changes.len(), 1); |
|
| 455 | + | assert_eq!(ups[0].name, "main"); |
|
| 456 | + | assert!(ups[0].force); |
|
| 457 | + | assert_eq!(ups[0].attestation, up.attestation); |
|
| 458 | + | let res = vec![RefResult { name: "main".into(), ok: false, reason: "stale".into() }]; |
|
| 459 | + | assert_eq!(wire::decode_push_response(&wire::encode_push_response(&res)).unwrap(), res); |
|
| 460 | + | assert_eq!(wire::decode_capabilities(&wire::encode_capabilities([7; 16])).unwrap(), [7; 16]); |
|
| 461 | + | } |
|
| 462 | + | } |
modified crates/oxy-core/src/repo.rs +29 −7
| … | 60 unchanged lines |
||
| 61 | 61 | pub bookmark: String, |
|
| 62 | 62 | /// `remote.<name>.url`. |
|
| 63 | 63 | pub remotes: BTreeMap<String, String>, |
|
| 64 | + | /// `remote.<name>.token`: bearer token for HTTP pushes. |
|
| 65 | + | pub remote_tokens: BTreeMap<String, String>, |
|
| 64 | 66 | } |
|
| 65 | 67 | ||
| 66 | 68 | impl Config { |
|
| … | 6 unchanged lines |
||
| 73 | 75 | sign_changes: false, |
|
| 74 | 76 | bookmark: "main".into(), |
|
| 75 | 77 | remotes: BTreeMap::new(), |
|
| 78 | + | remote_tokens: BTreeMap::new(), |
|
| 76 | 79 | } |
|
| 77 | 80 | } |
|
| 78 | 81 | ||
| … | 17 unchanged lines |
||
| 96 | 99 | } |
|
| 97 | 100 | } |
|
| 98 | 101 | "workspace.bookmark" => self.bookmark = value.into(), |
|
| 99 | - | k if k.starts_with("remote.") && k.ends_with(".url") => { |
|
| 100 | - | let name = &k["remote.".len()..k.len() - ".url".len()]; |
|
| 102 | + | k if k.starts_with("remote.") && (k.ends_with(".url") || k.ends_with(".token")) => { |
|
| 103 | + | let (name, is_url) = match k.strip_suffix(".url") { |
|
| 104 | + | Some(n) => (&n["remote.".len()..], true), |
|
| 105 | + | None => (&k["remote.".len()..k.len() - ".token".len()], false), |
|
| 106 | + | }; |
|
| 101 | 107 | if name.is_empty() || name.contains('/') { |
|
| 102 | 108 | return fail(format!("invalid remote name in {key:?}")); |
|
| 103 | 109 | } |
|
| 110 | + | let map = if is_url { &mut self.remotes } else { &mut self.remote_tokens }; |
|
| 104 | 111 | if value.is_empty() { |
|
| 105 | - | self.remotes.remove(name); |
|
| 112 | + | map.remove(name); |
|
| 113 | + | if is_url { |
|
| 114 | + | self.remote_tokens.remove(name); |
|
| 115 | + | } |
|
| 106 | 116 | } else { |
|
| 107 | - | self.remotes.insert(name.to_string(), value.into()); |
|
| 117 | + | map.insert(name.to_string(), value.into()); |
|
| 108 | 118 | } |
|
| 109 | 119 | } |
|
| 110 | 120 | _ => return fail(format!("unknown configuration key {key:?}")), |
|
| … | 26 unchanged lines |
||
| 137 | 147 | ); |
|
| 138 | 148 | for (name, url) in &self.remotes { |
|
| 139 | 149 | text.push_str(&format!("remote.{name}.url = \"{url}\"\n")); |
|
| 150 | + | if let Some(t) = self.remote_tokens.get(name) { |
|
| 151 | + | text.push_str(&format!("remote.{name}.token = \"{t}\"\n")); |
|
| 152 | + | } |
|
| 140 | 153 | } |
|
| 141 | 154 | write_atomic(path, text.as_bytes()) |
|
| 142 | 155 | } |
|
| … | 620 unchanged lines |
||
| 763 | 776 | self.config.remotes.get(name).cloned().ok_or_else(|| RepoError::Msg(format!("no remote named {name:?}; add one with `oxy remote add`"))) |
|
| 764 | 777 | } |
|
| 765 | 778 | ||
| 779 | + | /// Token for a remote: `remote.<name>.token`, else `$OXY_TOKEN`. |
|
| 780 | + | fn remote_token(&self, name: &str) -> Option<String> { |
|
| 781 | + | self.config.remote_tokens.get(name).cloned().or_else(|| std::env::var("OXY_TOKEN").ok()) |
|
| 782 | + | } |
|
| 783 | + | ||
| 784 | + | fn open_named_remote(&self, name: &str) -> Result<Box<dyn crate::protocol::Remote>> { |
|
| 785 | + | open_remote(&self.remote_url(name)?, self.remote_token(name)) |
|
| 786 | + | } |
|
| 787 | + | ||
| 766 | 788 | fn serial_path(&self, remote: &str, bookmark: &str) -> PathBuf { |
|
| 767 | 789 | self.oxy.join("remotes").join(remote).join(format!("{bookmark}.serial")) |
|
| 768 | 790 | } |
|
| … | 22 unchanged lines |
||
| 791 | 813 | ||
| 792 | 814 | /// Fetch every bookmark of a remote into `remotes/<name>/bookmarks/*` (spec §10.2). |
|
| 793 | 815 | pub fn fetch(&self, remote_name: &str) -> Result<Vec<(String, Vec<ChangeHash>, usize)>> { |
|
| 794 | - | let remote = open_remote(&self.remote_url(remote_name)?)?; |
|
| 816 | + | let remote = self.open_named_remote(remote_name)?; |
|
| 795 | 817 | if remote.repo_id()? != self.id()? { |
|
| 796 | 818 | return fail(format!("remote {remote_name:?} is a different repository (repository IDs differ)")); |
|
| 797 | 819 | } |
|
| … | 80 unchanged lines |
||
| 878 | 900 | /// Push the current bookmark (spec §16.3), sealing this author's unsigned |
|
| 879 | 901 | /// changes and attesting the reference when a key is available. |
|
| 880 | 902 | pub fn push(&self, remote_name: &str, force: bool) -> Result<Vec<RefResult>> { |
|
| 881 | - | let remote = open_remote(&self.remote_url(remote_name)?)?; |
|
| 903 | + | let remote = self.open_named_remote(remote_name)?; |
|
| 882 | 904 | if remote.repo_id()? != self.id()? { |
|
| 883 | 905 | return fail(format!("remote {remote_name:?} is a different repository (repository IDs differ)")); |
|
| 884 | 906 | } |
|
| … | 74 unchanged lines |
||
| 959 | 981 | /// Clone a remote into `dest`: same repository ID, `origin` remote, |
|
| 960 | 982 | /// bookmark set to the remote's, working copy checked out. |
|
| 961 | 983 | pub fn clone(url: &str, dest: &Path) -> Result<Repo> { |
|
| 962 | - | let remote = open_remote(url)?; |
|
| 984 | + | let remote = open_remote(url, std::env::var("OXY_TOKEN").ok())?; |
|
| 963 | 985 | let id = remote.repo_id()?; |
|
| 964 | 986 | let mut repo = Repo::init_with_id(dest, id)?; |
|
| 965 | 987 | repo.config.remotes.insert("origin".into(), url.to_string()); |
|
| … | 1552 unchanged lines |
modified src/main.rs +19 −1
| … | 58 unchanged lines |
||
| 59 | 59 | }, |
|
| 60 | 60 | /// Report which conflicts of the working copy's base are resolved by the current files |
|
| 61 | 61 | Resolve, |
|
| 62 | + | /// Serve repositories under ROOT over HTTP (spec §16); TLS is terminated in front |
|
| 63 | + | Serve { |
|
| 64 | + | /// Directory with one repository per subdirectory |
|
| 65 | + | #[arg(long, default_value = ".")] |
|
| 66 | + | root: PathBuf, |
|
| 67 | + | #[arg(long, default_value = "127.0.0.1:8890")] |
|
| 68 | + | bind: String, |
|
| 69 | + | /// Bearer token allowed to push (repeatable); none means open |
|
| 70 | + | #[arg(long = "token")] |
|
| 71 | + | tokens: Vec<String>, |
|
| 72 | + | }, |
|
| 62 | 73 | /// Undo the last operation |
|
| 63 | 74 | Undo, |
|
| 64 | 75 | /// Manage the signing identity |
|
| … | 3 unchanged lines |
||
| 68 | 79 | }, |
|
| 69 | 80 | /// Verify the signatures on a change (default: every change in the log) |
|
| 70 | 81 | Verify { change: Option<String> }, |
|
| 71 | - | /// Set a repository configuration key (author.name, author.email, author.key, core.sign, workspace.bookmark) |
|
| 82 | + | /// Set a repository configuration key (author.*, core.sign, workspace.bookmark, remote.<name>.url|token) |
|
| 72 | 83 | Config { key: String, value: String }, |
|
| 73 | 84 | /// List operations |
|
| 74 | 85 | #[command(name = "op")] |
|
| … | 317 unchanged lines |
||
| 392 | 403 | Ok(2) |
|
| 393 | 404 | } |
|
| 394 | 405 | } |
|
| 406 | + | Cmd::Serve { root, bind, tokens } => { |
|
| 407 | + | let listener = std::net::TcpListener::bind(&bind).with_context(|| format!("binding {bind}"))?; |
|
| 408 | + | let root = root.canonicalize()?; |
|
| 409 | + | println!("Serving repositories under {} on http://{bind}/<repo>/oxy/v1/ ({})", root.display(), if tokens.is_empty() { "open: no push tokens configured" } else { "push requires a token" }); |
|
| 410 | + | oxy_server::run_blocking(listener, oxy_server::ServerConfig { root, tokens })?; |
|
| 411 | + | Ok(0) |
|
| 412 | + | } |
|
| 395 | 413 | Cmd::Undo => { |
|
| 396 | 414 | let repo = open()?; |
|
| 397 | 415 | let op = repo.undo()?; |
|
| … | 72 unchanged lines |