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

@ -132,11 +132,15 @@ impl Activity {
/// Get all delivery targets as urls.
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.
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!()
}

View file

@ -2,6 +2,7 @@
use std::borrow::Borrow;
use std::pin::Pin;
use std::sync::Arc;
use crate::ap::Note;
use crate::{ Id, Result, ap::Actor, err };
@ -9,6 +10,9 @@ use futures::prelude::*;
use sqlx::{pool::PoolConnection, database::HasArguments};
use sqlx::{ FromRow, Either::Right };
use tokio::sync::{Mutex, mpsc};
use std::collections::HashSet as Set;
pub (crate) use self::data::*;
@ -57,6 +61,7 @@ impl Default for Config {
pub struct Client {
/// The internal connection pool.
pool: sqlx::Pool<Database>,
reserved: Arc<Mutex<Set<Id>>>,
}
impl Client {
@ -109,6 +114,33 @@ impl Client {
.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.
use sqlx::{ Type, FromRow };
use crate::{Id, ap::{Note, Create}, Result, Error};
use crate::{ Id, ap::Create };
/// Encodes the status of a follow request.
#[derive(Clone, Debug, Type)]

View file

@ -24,6 +24,8 @@ pub enum Error {
Url (url::ParseError),
/// An error in converting between models.
Invalid (ap::Invalid),
/// Generic "timed out" error.
Timeout,
}
/// 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
pub use id::Id;
pub use ctx::Context;
pub use ctx::{ Dereferencer, IdGenerator, Context };
// Module imports
pub mod conf;
@ -44,7 +44,7 @@ mod id {
use crate::db;
/// An ActivityPub identifier.
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
#[derive(PartialEq, Eq, Hash, Clone, Debug, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Id (reqwest::Url);
@ -98,7 +98,7 @@ mod ctx {
use rand::{Rng, prelude::StdRng, SeedableRng};
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.
///
@ -183,9 +183,10 @@ mod ctx {
todo!()
}
pub fn id_gen (&self) -> IdGenerator<StdRng> {
/// Get an [`IdGenerator`].
pub fn id_gen (&self) -> IdGenerator<'_, StdRng> {
IdGenerator {
hostname: self.config.host.clone(),
hostname: &self.config.host,
rng: StdRng::from_entropy(),
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.
fn web_client (&self) -> &reqwest::Client {
&self.web
@ -293,24 +289,31 @@ mod ctx {
}
/// How many bytes to generate for an ID.
const ID_LEN: usize = 64;
/// Generates [`Id`]s.
pub struct IdGenerator <R> {
hostname: String,
pub struct IdGenerator <'h, R> {
hostname: &'h str,
db: db::Client,
rng: R,
}
impl<R> IdGenerator<R>
impl<R> IdGenerator<'_, R>
where
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;
// Generate a random suffix
// Give up after 200 failed attempts.
let mut tries = 200;
loop {
// Generate a random suffix and encode it as base64.
let suffix = {
let mut buf = [0; ID_LEN];
rng.fill(&mut buf);
@ -319,9 +322,22 @@ mod ctx {
.replace("+", "_")
};
let id = format!("https://{hostname}/{prefix}/{suffix}").parse().ok()?;
// Create an actual `Id` with the generated garbage.
let id = format!("https://{hostname}/{prefix}/{suffix}").parse()?;
Some (id)
// 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)
}
}
}
}