Improvements to ID generation

This commit is contained in:
Riley Apeldoorn 2022-08-19 12:33:34 +02:00
parent 131312b33e
commit bf2b067b47
4 changed files with 82 additions and 28 deletions

View file

@ -3,7 +3,7 @@
use futures::prelude::*; use futures::prelude::*;
use serde::Serialize; use serde::Serialize;
use crate::{ Id, Activity, err, Result, Error, sign, ctx::Context, db::{Post, Privacy} }; use crate::{ Id, Activity, err, Result, Error, sign, ctx::Context, db::{ Post, Privacy } };
/// Represents the creation of an object. /// Represents the creation of an object.
/// ///
@ -132,11 +132,15 @@ impl Activity {
/// Get all delivery targets as urls. /// Get all delivery targets as urls.
async fn delivery_targets (&self) -> Result<Vec<reqwest::Url>> { async fn delivery_targets (&self) -> Result<Vec<reqwest::Url>> {
todo!() let unId = |Id (x)| x;
Ok (self.to().chain(self.cc()).map(unId).collect())
} }
/// Perform the activity. /// Perform the activity.
pub async fn perform <S> (self, ctx: &mut Context<S>) -> Result<()> where S: sign::Sign { pub async fn perform <S> (self, _: &mut Context<S>) -> Result<()>
where
S: sign::Sign
{
todo!() todo!()
} }

View file

@ -2,6 +2,7 @@
use std::borrow::Borrow; use std::borrow::Borrow;
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc;
use crate::ap::Note; use crate::ap::Note;
use crate::{ Id, Result, ap::Actor, err }; use crate::{ Id, Result, ap::Actor, err };
@ -9,6 +10,9 @@ use futures::prelude::*;
use sqlx::{pool::PoolConnection, database::HasArguments}; use sqlx::{pool::PoolConnection, database::HasArguments};
use sqlx::{ FromRow, Either::Right }; use sqlx::{ FromRow, Either::Right };
use tokio::sync::{Mutex, mpsc};
use std::collections::HashSet as Set;
pub (crate) use self::data::*; pub (crate) use self::data::*;
@ -57,6 +61,7 @@ impl Default for Config {
pub struct Client { pub struct Client {
/// The internal connection pool. /// The internal connection pool.
pool: sqlx::Pool<Database>, pool: sqlx::Pool<Database>,
reserved: Arc<Mutex<Set<Id>>>,
} }
impl Client { impl Client {
@ -109,6 +114,33 @@ impl Client {
.await .await
} }
/// Determine if an [`Id`] is already taken.
pub async fn verify_unique (&self, id: Id) -> Result<Option<Id>> {
let mut r = self.reserved.lock().await;
// First check the set of reserved ids.
if r.contains(&id) {
return Ok (None)
}
// If not, determine if it is taken in the database.
let is_taken: bool = self
.with_conn(|_| async {
todo!()
})
.await?;
// Reserve the id if it is not taken. This should prevent most race
// conditions in id generation.
Ok (if is_taken {
None
} else {
r.insert(id.clone());
Some (id)
})
}
} }
@ -226,7 +258,7 @@ pub mod data {
//! and in that case conversions between the two are needed. //! and in that case conversions between the two are needed.
use sqlx::{ Type, FromRow }; use sqlx::{ Type, FromRow };
use crate::{Id, ap::{Note, Create}, Result, Error}; use crate::{ Id, ap::Create };
/// Encodes the status of a follow request. /// Encodes the status of a follow request.
#[derive(Clone, Debug, Type)] #[derive(Clone, Debug, Type)]

View file

@ -24,6 +24,8 @@ pub enum Error {
Url (url::ParseError), Url (url::ParseError),
/// An error in converting between models. /// An error in converting between models.
Invalid (ap::Invalid), Invalid (ap::Invalid),
/// Generic "timed out" error.
Timeout,
} }
/// Trivial conversion function for use in `map_err` functions. /// Trivial conversion function for use in `map_err` functions.

View file

@ -10,7 +10,7 @@ use serde::Serialize;
// Expose the `Id` type in the crate root // Expose the `Id` type in the crate root
pub use id::Id; pub use id::Id;
pub use ctx::Context; pub use ctx::{ Dereferencer, IdGenerator, Context };
// Module imports // Module imports
pub mod conf; pub mod conf;
@ -44,7 +44,7 @@ mod id {
use crate::db; use crate::db;
/// An ActivityPub identifier. /// An ActivityPub identifier.
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] #[derive(PartialEq, Eq, Hash, Clone, Debug, Serialize, Deserialize)]
#[serde(transparent)] #[serde(transparent)]
pub struct Id (reqwest::Url); pub struct Id (reqwest::Url);
@ -98,7 +98,7 @@ mod ctx {
use rand::{Rng, prelude::StdRng, SeedableRng}; use rand::{Rng, prelude::StdRng, SeedableRng};
use serde_json::{Value, to_value}; use serde_json::{Value, to_value};
use crate::{ conf::Config, db, Result, sign::Sign, ap, Activity, Id, err }; use crate::{ conf::Config, db, Result, sign::Sign, ap, Activity, Id, err, Error };
/// The context of a thread/task. /// The context of a thread/task.
/// ///
@ -183,9 +183,10 @@ mod ctx {
todo!() todo!()
} }
pub fn id_gen (&self) -> IdGenerator<StdRng> { /// Get an [`IdGenerator`].
pub fn id_gen (&self) -> IdGenerator<'_, StdRng> {
IdGenerator { IdGenerator {
hostname: self.config.host.clone(), hostname: &self.config.host,
rng: StdRng::from_entropy(), rng: StdRng::from_entropy(),
db: self.client.clone(), db: self.client.clone(),
} }
@ -213,11 +214,6 @@ mod ctx {
} }
} }
/// Get the inner database client.
fn db_client (&self) -> &db::Client {
&self.db
}
/// Get the inner web client. /// Get the inner web client.
fn web_client (&self) -> &reqwest::Client { fn web_client (&self) -> &reqwest::Client {
&self.web &self.web
@ -293,35 +289,55 @@ mod ctx {
} }
/// How many bytes to generate for an ID.
const ID_LEN: usize = 64; const ID_LEN: usize = 64;
/// Generates [`Id`]s. /// Generates [`Id`]s.
pub struct IdGenerator <R> { pub struct IdGenerator <'h, R> {
hostname: String, hostname: &'h str,
db: db::Client, db: db::Client,
rng: R, rng: R,
} }
impl<R> IdGenerator<R> impl<R> IdGenerator<'_, R>
where where
R: Rng R: Rng
{ {
pub fn gen (&mut self, prefix: &str) -> Option<Id> { /// Generate an [`Id`] with the given prefix.
pub async fn gen (&mut self, prefix: &str) -> Result<Id> {
let IdGenerator { rng, db, hostname } = self; let IdGenerator { rng, db, hostname } = self;
// Generate a random suffix // Give up after 200 failed attempts.
let suffix = { let mut tries = 200;
let mut buf = [0; ID_LEN];
rng.fill(&mut buf);
base64::encode_block(&buf)
.replace("=", "-")
.replace("+", "_")
};
let id = format!("https://{hostname}/{prefix}/{suffix}").parse().ok()?; loop {
Some (id) // Generate a random suffix and encode it as base64.
let suffix = {
let mut buf = [0; ID_LEN];
rng.fill(&mut buf);
base64::encode_block(&buf)
.replace("=", "-")
.replace("+", "_")
};
// Create an actual `Id` with the generated garbage.
let id = format!("https://{hostname}/{prefix}/{suffix}").parse()?;
// Basic check to see if the given id exists. This reserves the id.
// If the id is already taken, it is consumed.
if let Some (id) = db.verify_unique(id).await? {
break Ok (id)
}
tries -= 1;
if tries == 0 {
// After our tries are exhausted, give up.
break Err (Error::Timeout)
}
}
} }
} }