diff --git a/Cargo.lock b/Cargo.lock index c9257a0..1ff565e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1074,6 +1074,7 @@ dependencies = [ "ed25519-dalek", "hex_fmt", "lazy_static", + "prometheus", "rand 0.8.5", "serde", "serde_with", @@ -1458,6 +1459,27 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prometheus" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "memchr", + "parking_lot 0.12.1", + "protobuf", + "thiserror", +] + +[[package]] +name = "protobuf" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" + [[package]] name = "quote" version = "1.0.32" diff --git a/crates/lox-distributor/src/db_handler.rs b/crates/lox-distributor/src/db_handler.rs index 133dbd0..d7a9bf4 100644 --- a/crates/lox-distributor/src/db_handler.rs +++ b/crates/lox-distributor/src/db_handler.rs @@ -123,12 +123,13 @@ fn use_last_context(lox_db: sled::Db) -> lox_context::LoxServerContext { #[cfg(test)] mod tests { use super::lox_context::LoxServerContext; - use super::DB; use super::DbConfig; + use super::DB; #[test] fn test_write_context() { - let (mut lox_db, _context) = DB::open_new_or_existing_db(DbConfig::default(), None).unwrap(); + let (mut lox_db, _context) = + DB::open_new_or_existing_db(DbConfig::default(), None).unwrap(); assert!( lox_db.db.is_empty(), "db read from context that shouldn't exist" diff --git a/crates/lox-distributor/src/lox_context.rs b/crates/lox-distributor/src/lox_context.rs index 492e17f..76e139a 100644 --- a/crates/lox-distributor/src/lox_context.rs +++ b/crates/lox-distributor/src/lox_context.rs @@ -6,7 +6,7 @@ use lox_library::{ blockage_migration, check_blockage, issue_invite, level_up, migration, open_invite, redeem_invite, trust_promotion, }, - BridgeAuth, BridgeDb, IssuerPubKey, + BridgeAuth, BridgeDb, ExceededMaxBridgesError, IssuerPubKey, }; use rdsys_backend::proto::{Resource, ResourceState}; use serde::{Deserialize, Serialize}; @@ -317,10 +317,11 @@ impl LoxServerContext { ] } - fn gen_invite(&self) -> lox_utils::Invite { + fn gen_invite(&self) -> Result { let mut obj = self.db.lock().unwrap(); - lox_utils::Invite { - invite: obj.invite(), + match obj.invite() { + Ok(invite) => Ok(lox_utils::Invite { invite }), + Err(e) => Err(e), } } @@ -382,8 +383,14 @@ impl LoxServerContext { // Generate and return an open invitation token pub fn generate_invite(self) -> Response { let invite = self.gen_invite(); - match serde_json::to_string(&invite) { - Ok(resp) => prepare_header(resp), + match invite { + Ok(invite) => match serde_json::to_string(&invite) { + Ok(resp) => prepare_header(resp), + Err(e) => { + println!("Error parsing Invite to JSON"); + prepare_error_header(e.to_string()) + } + }, Err(e) => { println!("Error parsing Invite to JSON"); prepare_error_header(e.to_string()) diff --git a/crates/lox-library/Cargo.toml b/crates/lox-library/Cargo.toml index 087aa33..647f32c 100644 --- a/crates/lox-library/Cargo.toml +++ b/crates/lox-library/Cargo.toml @@ -23,6 +23,7 @@ hex_fmt = "0.3" aes-gcm = { version = "0.10", features =["aes"]} base64 = "0.21" time = "0.3.30" +prometheus = "0.13.3" subtle = "2.5" thiserror = "1.0.49" zkp = { git = "https://gitlab.torproject.org/onyinyang/lox-zkp" } diff --git a/crates/lox-library/src/lib.rs b/crates/lox-library/src/lib.rs index c929e83..ee75858 100644 --- a/crates/lox-library/src/lib.rs +++ b/crates/lox-library/src/lib.rs @@ -22,6 +22,7 @@ pub mod cred; pub mod dup_filter; pub mod migration_table; +use chrono::Duration; use chrono::{DateTime, Utc}; use sha2::Sha512; @@ -78,6 +79,12 @@ pub enum NoAvailableIDError { ExhaustedIndexer, } +#[derive(Error, Debug)] +pub enum ExceededMaxBridgesError { + #[error("The maximum number of bridges has already been distributed today, please try again tomorrow!")] + ExceededMaxBridges, +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct IssuerPrivKey { x0tilde: Scalar, @@ -123,7 +130,10 @@ impl IssuerPubKey { } } +// Number of times a given invitation is ditributed pub const OPENINV_K: u32 = 10; +// TODO: Decide on maximum daily number of invitations to be distributed +pub const MAX_DAILY_BRIDGES: u32 = 100; /// The BridgeDb. This will typically be a singleton object. The /// BridgeDb's role is simply to issue signed "open invitations" to /// people who are not yet part of the system. @@ -136,7 +146,10 @@ pub struct BridgeDb { /// The set of open-invitation buckets openinv_buckets: HashSet, distributed_buckets: Vec, - current_k: u32, + #[serde(skip)] + today: DateTime, + pub current_k: u32, + pub daily_bridges_distributed: u32, } /// An open invitation is a [u8; OPENINV_LENGTH] where the first 32 @@ -159,7 +172,9 @@ impl BridgeDb { pubkey, openinv_buckets: Default::default(), distributed_buckets: Default::default(), + today: Utc::now(), current_k: 0, + daily_bridges_distributed: 0, } } @@ -189,30 +204,39 @@ impl BridgeDb { /// Produce an open invitation such that the next k users, where k is < /// OPENINV_K, will receive the same open invitation bucket /// chosen randomly from the set of open-invitation buckets. - pub fn invite(&mut self) -> [u8; OPENINV_LENGTH] { + pub fn invite(&mut self) -> Result<[u8; OPENINV_LENGTH], ExceededMaxBridgesError> { let mut res: [u8; OPENINV_LENGTH] = [0; OPENINV_LENGTH]; let mut rng = rand::thread_rng(); // Choose a random invitation id (a Scalar) and serialize it let id = Scalar::random(&mut rng); res[0..32].copy_from_slice(&id.to_bytes()); let bucket_num: u32; - if self.current_k < OPENINV_K && !self.distributed_buckets.is_empty() { - bucket_num = *self.distributed_buckets.last().unwrap(); - self.current_k += 1; - } else { - // Choose a random bucket number (from the set of open - // invitation buckets) and serialize it - let openinv_vec: Vec<&u32> = self.openinv_buckets.iter().collect(); - bucket_num = *openinv_vec[rng.gen_range(0..openinv_vec.len())]; - self.mark_distributed(bucket_num); - self.remove_openinv(&bucket_num); - self.current_k = 1; + if Utc::now() >= (self.today + Duration::days(1)) { + self.today = Utc::now(); + self.daily_bridges_distributed = 0; + } + if self.daily_bridges_distributed < MAX_DAILY_BRIDGES { + if self.current_k < OPENINV_K && !self.distributed_buckets.is_empty() { + bucket_num = *self.distributed_buckets.last().unwrap(); + self.current_k += 1; + } else { + // Choose a random bucket number (from the set of open + // invitation buckets) and serialize it + let openinv_vec: Vec<&u32> = self.openinv_buckets.iter().collect(); + bucket_num = *openinv_vec[rng.gen_range(0..openinv_vec.len())]; + self.mark_distributed(bucket_num); + self.remove_openinv(&bucket_num); + self.current_k = 1; + self.daily_bridges_distributed += 1; + } + res[32..(32 + 4)].copy_from_slice(&bucket_num.to_le_bytes()); + // Sign the first 36 bytes and serialize it + let sig = self.keypair.sign(&res[0..(32 + 4)]); + res[(32 + 4)..].copy_from_slice(&sig.to_bytes()); + Ok(res) + } else { + Err(ExceededMaxBridgesError::ExceededMaxBridges) } - res[32..(32 + 4)].copy_from_slice(&bucket_num.to_le_bytes()); - // Sign the first 36 bytes and serialize it - let sig = self.keypair.sign(&res[0..(32 + 4)]); - res[(32 + 4)..].copy_from_slice(&sig.to_bytes()); - res } /// Verify an open invitation. Returns the invitation id and the diff --git a/crates/lox-library/src/tests.rs b/crates/lox-library/src/tests.rs index 83fd061..3c47e84 100644 --- a/crates/lox-library/src/tests.rs +++ b/crates/lox-library/src/tests.rs @@ -64,7 +64,7 @@ impl TestHarness { fn open_invite(&mut self) -> (PerfStat, (cred::Lox, bridge_table::BridgeLine)) { // Issue an open invitation - let inv = self.bdb.invite(); + let inv = self.bdb.invite().unwrap(); let req_start = Instant::now(); // Use it to get a Lox credential diff --git a/crates/lox-library/tests/tests.rs b/crates/lox-library/tests/tests.rs index a270194..9bc376f 100644 --- a/crates/lox-library/tests/tests.rs +++ b/crates/lox-library/tests/tests.rs @@ -10,7 +10,7 @@ fn test_bridgedb() { for i in &[1u32, 5, 7, 12, 19, 20, 22] { bdb.insert_openinv(*i); } - let inv = bdb.invite(); + let inv = bdb.invite().unwrap(); println!("{:?}", inv); let res = BridgeDb::verify(inv, bdb.pubkey); println!("{:?}", res);