Skip to content

Commit

Permalink
feat: Add TagGenerator
Browse files Browse the repository at this point in the history
  • Loading branch information
duesee authored Oct 27, 2023
1 parent 38631e1 commit 7c18692
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 1 deletion.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ tokio = { version = "1.32.0", features = ["macros", "net", "rt"] }
[workspace]
resolver = "2"
members = [
"proxy"
"proxy",
"tag-generator",
]
8 changes: 8 additions & 0 deletions tag-generator/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "tag-generator"
version = "0.1.0"
edition = "2021"

[dependencies]
imap-types = { version = "1.0.0", features = ["unvalidated"] }
rand = "0.8.5"
92 changes: 92 additions & 0 deletions tag-generator/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use std::sync::atomic::{AtomicU64, Ordering};

use imap_types::core::Tag;
#[cfg(not(debug_assertions))]
use rand::distributions::{Alphanumeric, DistString};

static GLOBAL_TAG_GENERATOR_COUNT: AtomicU64 = AtomicU64::new(0);

#[derive(Debug)]
pub struct TagGenerator {
global: u64,
counter: u64,
}

impl TagGenerator {
/// Generate an instance of a `TagGenerator`
///
/// Returns a `TagGenerator` generating tags with a unique prefix.
pub fn new() -> TagGenerator {
// There is no synchronization required and we only care about each thread seeing a unique value.
let global = GLOBAL_TAG_GENERATOR_COUNT.fetch_add(1, Ordering::Relaxed);
let counter = 0;

TagGenerator { global, counter }
}

/// Generate a unique `Tag`
///
/// The tag has the form `<Instance>.<Counter>.<Random>`, and is guaranteed to be unique and not
/// guessable ("forward-secure").
///
/// Rational: `Instance` and `Counter` improve IMAP trace readability.
/// The non-guessable `Random` hampers protocol-confusion attacks (to a limiting extend).
pub fn generate(&mut self) -> Tag<'static> {
#[cfg(not(debug_assertions))]
let inner = {
let token = Alphanumeric.sample_string(&mut rand::thread_rng(), 8);
format!("{}.{}.{token}", self.global, self.counter)
};

// Minimize randomness lending the library for security analysis.
#[cfg(debug_assertions)]
let inner = format!("{}.{}", self.global, self.counter);

let tag = Tag::unvalidated(inner);
self.counter = self.counter.wrapping_add(1);
tag
}
}

#[cfg(test)]
mod tests {
use std::{collections::BTreeSet, thread, time::Duration};

use rand::random;

use super::TagGenerator;

#[test]
fn test_generator_generator() {
const THREADS: usize = 1000;
const INVOCATIONS: usize = 5;
const TOTAL_INVOCATIONS: usize = THREADS * INVOCATIONS;

let (sender, receiver) = std::sync::mpsc::channel();

thread::scope(|s| {
for _ in 1..=THREADS {
let sender = sender.clone();

s.spawn(move || {
let mut generator = TagGenerator::new();
thread::sleep(Duration::from_millis(random::<u8>() as u64));

for _ in 1..=INVOCATIONS {
sender.send(generator.generate()).unwrap();
}
});
}

let mut set = BTreeSet::new();

while set.len() != TOTAL_INVOCATIONS {
let tag = receiver.recv().unwrap();

// Make sure insertion worked, i.e., no duplicate was found.
// Note: `Tag` doesn't implement `Ord` so we insert a `String`.
assert!(set.insert(tag.as_ref().to_owned()), "duplicate tag found");
}
});
}
}

0 comments on commit 7c18692

Please sign in to comment.