diff --git a/Cargo.lock b/Cargo.lock index 68f3ab5..9199a20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] @@ -362,9 +362,9 @@ checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "cc" -version = "1.1.22" +version = "1.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9540e661f81799159abee814118cc139a2004b3a3aa3ea37724a1b66530b90e0" +checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" dependencies = [ "shlex", ] @@ -388,9 +388,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.18" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", @@ -398,9 +398,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.18" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", @@ -520,9 +520,9 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "critical-section" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f64009896348fc5af4222e9cf7d7d82a95a256c634ebcf61c53e4ea461422242" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" [[package]] name = "crossbeam-queue" @@ -899,9 +899,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "h2" @@ -933,9 +933,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] name = "headers" @@ -1067,9 +1067,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -1079,9 +1079,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" dependencies = [ "bytes", "futures-channel", @@ -1173,9 +1173,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown", @@ -1192,9 +1192,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "is_terminal_polyfill" @@ -1210,9 +1210,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -1225,9 +1225,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "f0b21006cd1874ae9e650973c565615676dc4a274c965bb0a73796dac838ce4f" [[package]] name = "libredox" @@ -1280,9 +1280,9 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lru" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" [[package]] name = "mainline" @@ -1396,21 +1396,18 @@ checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "object" -version = "0.36.4" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.20.1" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" -dependencies = [ - "portable-atomic", -] +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opaque-debug" @@ -1420,9 +1417,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ "bitflags", "cfg-if", @@ -1452,9 +1449,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -1575,7 +1572,7 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkarr" version = "2.2.0" -source = "git+https://github.com/Pubky/pkarr?branch=serde#680ec9303f5f3ee4b40dc53c0071c4088d4eb6ff" +source = "git+https://github.com/Pubky/pkarr?branch=serde#61232ffa1b81be20530dfb81fe31273e2c860977" dependencies = [ "base32", "bytes", @@ -1625,12 +1622,6 @@ dependencies = [ "universal-hash", ] -[[package]] -name = "portable-atomic" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" - [[package]] name = "postcard" version = "1.0.10" @@ -1661,9 +1652,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" dependencies = [ "unicode-ident", ] @@ -1705,11 +1696,27 @@ dependencies = [ "once_cell", "pkarr", "postcard", + "pubky-timestamp", "rand", "serde", "thiserror", ] +[[package]] +name = "pubky-timestamp" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084b6e5bfcc186781b71257d636b660f20e94bb588c3ba52393fd9faf7a7bfda" +dependencies = [ + "base32", + "document-features", + "getrandom", + "httpdate", + "js-sys", + "once_cell", + "serde", +] + [[package]] name = "pubky_homeserver" version = "0.1.0" @@ -2022,9 +2029,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.13" +version = "0.23.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +checksum = "5fbb44d7acc4e873d613422379f69f237a1b141928c02f6bc6ccfddddc2d7993" dependencies = [ "once_cell", "ring", @@ -2036,19 +2043,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" [[package]] name = "rustls-webpki" @@ -2063,9 +2069,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "ryu" @@ -2732,9 +2738,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" @@ -2819,9 +2825,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", @@ -2830,9 +2836,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", @@ -2845,9 +2851,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -2857,9 +2863,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2867,9 +2873,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", @@ -2880,15 +2886,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 04d0af3..1e59b0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ members = [ resolver = "2" [workspace.dependencies] -pkarr = { git = "https://github.com/Pubky/pkarr", branch = "serde", package = "pkarr", features = ["async"] } +pkarr = { git = "https://github.com/Pubky/pkarr", branch = "serde", package = "pkarr", features = ["async", "serde"] } serde = { version = "^1.0.209", features = ["derive"] } [profile.release] diff --git a/pubky-common/Cargo.toml b/pubky-common/Cargo.toml index 9676fba..faf1d3d 100644 --- a/pubky-common/Cargo.toml +++ b/pubky-common/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" [dependencies] base32 = "0.5.0" blake3 = "1.5.1" -ed25519-dalek = "2.1.1" +ed25519-dalek = { version = "2.1.1", features = ["serde"] } once_cell = "1.19.0" pkarr = { workspace = true } rand = "0.8.5" @@ -17,17 +17,11 @@ postcard = { version = "1.0.8", features = ["alloc"] } crypto_secretbox = { version = "0.1.1", features = ["std"] } argon2 = { version = "0.5.3", features = ["std"] } -serde = { workspace = true, optional = true } +serde = { workspace = true } +pubky-timestamp = { version = "0.2.0", features = ["full"] } [target.'cfg(target_arch = "wasm32")'.dependencies] js-sys = "0.3.69" [dev-dependencies] postcard = "1.0.8" - -[features] - -serde = ["dep:serde", "ed25519-dalek/serde", "pkarr/serde"] -full = ['serde'] - -default = ['full'] diff --git a/pubky-common/src/auth.rs b/pubky-common/src/auth.rs index 866fe5e..082b8e8 100644 --- a/pubky-common/src/auth.rs +++ b/pubky-common/src/auth.rs @@ -76,7 +76,7 @@ impl AuthToken { let now = Timestamp::now(); // Chcek timestamp; - let diff = token.timestamp.difference(&now); + let diff = token.timestamp.as_u64() as i64 - now.as_u64() as i64; if diff > TIMESTAMP_WINDOW { return Err(Error::TooFarInTheFuture); } @@ -155,7 +155,7 @@ impl AuthVerifier { /// Remove all tokens older than two time intervals in the past. fn gc(&self) { - let threshold = ((Timestamp::now().into_inner() / TIME_INTERVAL) - 2).to_be_bytes(); + let threshold = ((Timestamp::now().as_u64() / TIME_INTERVAL) - 2).to_be_bytes(); let mut inner = self.seen.lock().unwrap(); @@ -235,7 +235,7 @@ mod tests { let verifier = AuthVerifier::default(); - let timestamp = (&Timestamp::now()) - (TIMESTAMP_WINDOW as u64); + let timestamp = (Timestamp::now()) - (TIMESTAMP_WINDOW as u64); let mut signable = vec![]; signable.extend_from_slice(signer.public_key().as_bytes()); diff --git a/pubky-common/src/lib.rs b/pubky-common/src/lib.rs index cfb56f2..b19a187 100644 --- a/pubky-common/src/lib.rs +++ b/pubky-common/src/lib.rs @@ -4,4 +4,7 @@ pub mod crypto; pub mod namespaces; pub mod recovery_file; pub mod session; -pub mod timestamp; + +pub mod timestamp { + pub use pubky_timestamp::*; +} diff --git a/pubky-common/src/session.rs b/pubky-common/src/session.rs index 972652c..6b17735 100644 --- a/pubky-common/src/session.rs +++ b/pubky-common/src/session.rs @@ -26,7 +26,7 @@ impl Session { Self { version: 0, pubky: token.pubky().to_owned(), - created_at: Timestamp::now().into_inner(), + created_at: Timestamp::now().as_u64(), capabilities: token.capabilities().to_vec(), user_agent: user_agent.as_deref().unwrap_or("").to_string(), name: user_agent.as_deref().unwrap_or("").to_string(), diff --git a/pubky-common/src/timestamp.rs b/pubky-common/src/timestamp.rs deleted file mode 100644 index c3c9846..0000000 --- a/pubky-common/src/timestamp.rs +++ /dev/null @@ -1,280 +0,0 @@ -//! Strictly monotonic unix timestamp in microseconds - -use serde::{Deserialize, Serialize}; -use std::fmt::Display; -use std::{ - ops::{Add, Sub}, - sync::Mutex, -}; - -use once_cell::sync::Lazy; -use rand::Rng; - -#[cfg(not(target_arch = "wasm32"))] -use std::time::SystemTime; - -/// ~4% chance of none of 10 clocks have matching id. -const CLOCK_MASK: u64 = (1 << 8) - 1; -const TIME_MASK: u64 = !0 >> 8; - -pub struct TimestampFactory { - clock_id: u64, - last_time: u64, -} - -impl TimestampFactory { - pub fn new() -> Self { - Self { - clock_id: rand::thread_rng().gen::() & CLOCK_MASK, - last_time: system_time() & TIME_MASK, - } - } - - pub fn now(&mut self) -> Timestamp { - // Ensure strict monotonicity. - self.last_time = (system_time() & TIME_MASK).max(self.last_time + CLOCK_MASK + 1); - - // Add clock_id to the end of the timestamp - Timestamp(self.last_time | self.clock_id) - } -} - -impl Default for TimestampFactory { - fn default() -> Self { - Self::new() - } -} - -static DEFAULT_FACTORY: Lazy> = - Lazy::new(|| Mutex::new(TimestampFactory::default())); - -/// STrictly monotonic timestamp since [SystemTime::UNIX_EPOCH] in microseconds as u64. -/// -/// The purpose of this timestamp is to unique per "user", not globally, -/// it achieves this by: -/// 1. Override the last byte with a random `clock_id`, reducing the probability -/// of two matching timestamps across multiple machines/threads. -/// 2. Gurantee that the remaining 3 bytes are ever increasing (strictly monotonic) within -/// the same thread regardless of the wall clock value -/// -/// This timestamp is also serialized as BE bytes to remain sortable. -/// If a `utf-8` encoding is necessary, it is encoded as [base32::Alphabet::Crockford] -/// to act as a sortable Id. -/// -/// U64 of microseconds is valid for the next 500 thousand years! -#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)] -pub struct Timestamp(u64); - -impl Timestamp { - pub fn now() -> Self { - DEFAULT_FACTORY.lock().unwrap().now() - } - - /// Return big endian bytes - pub fn to_bytes(&self) -> [u8; 8] { - self.0.to_be_bytes() - } - - pub fn difference(&self, rhs: &Timestamp) -> i64 { - (self.0 as i64) - (rhs.0 as i64) - } - - pub fn into_inner(&self) -> u64 { - self.0 - } -} - -impl Default for Timestamp { - fn default() -> Self { - Timestamp::now() - } -} - -impl Display for Timestamp { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let bytes: [u8; 8] = self.into(); - f.write_str(&base32::encode(base32::Alphabet::Crockford, &bytes)) - } -} - -impl TryFrom for Timestamp { - type Error = TimestampError; - - fn try_from(value: String) -> Result { - match base32::decode(base32::Alphabet::Crockford, &value) { - Some(vec) => { - let bytes: [u8; 8] = vec - .try_into() - .map_err(|_| TimestampError::InvalidEncoding)?; - - Ok(bytes.into()) - } - None => Err(TimestampError::InvalidEncoding), - } - } -} - -impl TryFrom<&[u8]> for Timestamp { - type Error = TimestampError; - - fn try_from(bytes: &[u8]) -> Result { - let bytes: [u8; 8] = bytes - .try_into() - .map_err(|_| TimestampError::InvalidBytesLength(bytes.len()))?; - - Ok(bytes.into()) - } -} - -impl From<&Timestamp> for [u8; 8] { - fn from(timestamp: &Timestamp) -> Self { - timestamp.0.to_be_bytes() - } -} - -impl From<[u8; 8]> for Timestamp { - fn from(bytes: [u8; 8]) -> Self { - Self(u64::from_be_bytes(bytes)) - } -} - -// === U64 conversion === - -impl From for u64 { - fn from(value: Timestamp) -> Self { - value.into_inner() - } -} - -impl Add for &Timestamp { - type Output = Timestamp; - - fn add(self, rhs: u64) -> Self::Output { - Timestamp(self.0 + rhs) - } -} - -impl Sub for &Timestamp { - type Output = Timestamp; - - fn sub(self, rhs: u64) -> Self::Output { - Timestamp(self.0 - rhs) - } -} - -impl Serialize for Timestamp { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let bytes = self.to_bytes(); - bytes.serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for Timestamp { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let bytes: [u8; 8] = Deserialize::deserialize(deserializer)?; - Ok(Timestamp(u64::from_be_bytes(bytes))) - } -} - -#[cfg(not(target_arch = "wasm32"))] -/// Return the number of microseconds since [SystemTime::UNIX_EPOCH] -fn system_time() -> u64 { - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("time drift") - .as_micros() as u64 -} - -#[cfg(target_arch = "wasm32")] -/// Return the number of microseconds since [SystemTime::UNIX_EPOCH] -pub fn system_time() -> u64 { - // Won't be an issue for more than 5000 years! - (js_sys::Date::now() as u64 ) - // Turn miliseconds to microseconds - * 1000 -} - -#[derive(thiserror::Error, Debug)] -pub enum TimestampError { - #[error("Invalid bytes length, Timestamp should be encoded as 8 bytes, got {0}")] - InvalidBytesLength(usize), - #[error("Invalid timestamp encoding")] - InvalidEncoding, -} - -#[cfg(test)] -mod tests { - use std::collections::HashSet; - - use super::*; - - #[test] - fn strictly_monotonic() { - const COUNT: usize = 100; - - let mut set = HashSet::with_capacity(COUNT); - let mut vec = Vec::with_capacity(COUNT); - - for _ in 0..COUNT { - let timestamp = Timestamp::now(); - - set.insert(timestamp.clone()); - vec.push(timestamp); - } - - let mut ordered = vec.clone(); - ordered.sort(); - - assert_eq!(set.len(), COUNT, "unique"); - assert_eq!(ordered, vec, "ordered"); - } - - #[test] - fn strings() { - const COUNT: usize = 100; - - let mut set = HashSet::with_capacity(COUNT); - let mut vec = Vec::with_capacity(COUNT); - - for _ in 0..COUNT { - let string = Timestamp::now().to_string(); - - set.insert(string.clone()); - vec.push(string) - } - - let mut ordered = vec.clone(); - ordered.sort(); - - assert_eq!(set.len(), COUNT, "unique"); - assert_eq!(ordered, vec, "ordered"); - } - - #[test] - fn to_from_string() { - let timestamp = Timestamp::now(); - let string = timestamp.to_string(); - let decoded: Timestamp = string.try_into().unwrap(); - - assert_eq!(decoded, timestamp) - } - - #[test] - fn serde() { - let timestamp = Timestamp::now(); - - let serialized = postcard::to_allocvec(×tamp).unwrap(); - - assert_eq!(serialized, timestamp.to_bytes()); - - let deserialized: Timestamp = postcard::from_bytes(&serialized).unwrap(); - - assert_eq!(deserialized, timestamp); - } -} diff --git a/pubky-homeserver/src/routes.rs b/pubky-homeserver/src/routes.rs index 2105e86..6404ffb 100644 --- a/pubky-homeserver/src/routes.rs +++ b/pubky-homeserver/src/routes.rs @@ -1,6 +1,6 @@ use axum::{ extract::DefaultBodyLimit, - routing::{delete, get, post, put}, + routing::{delete, get, head, post, put}, Router, }; use tower_cookies::CookieManagerLayer; @@ -25,6 +25,7 @@ fn base(state: AppState) -> Router { .route("/:pubky/session", delete(auth::signout)) .route("/:pubky/*path", put(public::put)) .route("/:pubky/*path", get(public::get)) + .route("/:pubky/*path", head(public::head)) .route("/:pubky/*path", delete(public::delete)) .route("/events/", get(feed::feed)) .layer(CookieManagerLayer::new()) diff --git a/pubky-homeserver/src/routes/auth.rs b/pubky-homeserver/src/routes/auth.rs index 1075f48..eaf3a57 100644 --- a/pubky-homeserver/src/routes/auth.rs +++ b/pubky-homeserver/src/routes/auth.rs @@ -104,7 +104,7 @@ pub async fn signin( &mut wtxn, public_key, &User { - created_at: Timestamp::now().into_inner(), + created_at: Timestamp::now().as_u64(), }, )?; } diff --git a/pubky-homeserver/src/routes/feed.rs b/pubky-homeserver/src/routes/feed.rs index 6271aeb..a54b8a5 100644 --- a/pubky-homeserver/src/routes/feed.rs +++ b/pubky-homeserver/src/routes/feed.rs @@ -4,7 +4,7 @@ use axum::{ http::{header, Response, StatusCode}, response::IntoResponse, }; -use pubky_common::timestamp::{Timestamp, TimestampError}; +use pubky_common::timestamp::Timestamp; use crate::{ error::{Error, Result}, @@ -17,17 +17,11 @@ pub async fn feed( params: ListQueryParams, ) -> Result { if let Some(ref cursor) = params.cursor { - if let Err(timestmap_error) = Timestamp::try_from(cursor.to_string()) { - let cause = match timestmap_error { - TimestampError::InvalidEncoding => { - "Cursor should be valid base32 Crockford encoding of a timestamp" - } - TimestampError::InvalidBytesLength(size) => { - &format!("Cursor should be 13 characters long, got: {size}") - } - }; - - Err(Error::new(StatusCode::BAD_REQUEST, cause.into()))? + if Timestamp::try_from(cursor.to_string()).is_err() { + Err(Error::new( + StatusCode::BAD_REQUEST, + "Cursor should be valid base32 Crockford encoding of a timestamp".into(), + ))? } } diff --git a/pubky-homeserver/src/routes/public.rs b/pubky-homeserver/src/routes/public.rs index bd61e23..0e91ae9 100644 --- a/pubky-homeserver/src/routes/public.rs +++ b/pubky-homeserver/src/routes/public.rs @@ -1,7 +1,7 @@ use axum::{ body::Body, extract::State, - http::{header, Response, StatusCode}, + http::{header, HeaderMap, HeaderValue, Response, StatusCode}, response::IntoResponse, }; use futures_util::stream::StreamExt; @@ -125,6 +125,26 @@ pub async fn get( } } +pub async fn head( + State(state): State, + pubky: Pubky, + path: EntryPath, +) -> Result { + verify(path.as_str())?; + + let rtxn = state.db.env.read_txn()?; + + match state + .db + .get_entry(&rtxn, pubky.public_key(), path.as_str())? + .as_ref() + .map(HeaderMap::from) + { + Some(headers) => Ok(headers), + None => Err(Error::with_status(StatusCode::NOT_FOUND)), + } +} + pub async fn delete( State(mut state): State, pubky: Pubky, @@ -188,3 +208,32 @@ fn verify(path: &str) -> Result<()> { Ok(()) } + +impl From<&Entry> for HeaderMap { + fn from(entry: &Entry) -> Self { + let mut headers = HeaderMap::new(); + headers.insert(header::CONTENT_LENGTH, entry.content_length().into()); + headers.insert( + header::LAST_MODIFIED, + HeaderValue::from_str(&entry.timestamp().format_http_date()) + .expect("http date is valid header value"), + ); + headers.insert( + header::CONTENT_TYPE, + // TODO: when setting content type from user input, we should validate it as a HeaderValue + entry + .content_type() + .try_into() + .or(HeaderValue::from_str("")) + .expect("valid header value"), + ); + headers.insert( + header::ETAG, + format!("\"{}\"", entry.content_hash()) + .try_into() + .expect("hex string is valid"), + ); + + headers + } +}