mail_core/default_impl/
message_id_gen.rs1use std::sync::atomic::{AtomicUsize, Ordering};
2use std::collections::hash_map::DefaultHasher;
3use std::hash::Hasher;
4
5use rand;
6use soft_ascii_string::SoftAsciiString;
7
8use internals::error::EncodingError;
9use headers::header_components::{MessageId, ContentId, Domain};
10use ::context::MailIdGenComponent;
11
12
13static MAIL_COUNTER: AtomicUsize = AtomicUsize::new(0);
14
15fn counter_next() -> usize {
16 MAIL_COUNTER.fetch_add(1, Ordering::AcqRel)
17}
18
19fn anonymize_through_random_hash(num: usize) -> u64 {
20 let rnum = rand::random::<u32>();
21 let mut hasher = DefaultHasher::new();
22 hasher.write_usize(num);
23 hasher.write_u32(rnum);
24 hasher.finish()
25}
26
27fn gen_next_program_unique_number() -> u64 {
28 anonymize_through_random_hash(counter_next())
29}
30
31#[derive(Debug, Clone)]
33pub struct HashedIdGen {
34 domain: SoftAsciiString,
35 part_unique_in_domain: SoftAsciiString
36}
37
38impl HashedIdGen {
39
40
41 pub fn new(domain: Domain, part_unique_in_domain: SoftAsciiString)
79 -> Result<Self, EncodingError>
80 {
81 let domain = domain.into_ascii_string()?;
82 Ok(HashedIdGen {
83 domain,
84 part_unique_in_domain
85 })
86 }
87}
88
89impl MailIdGenComponent for HashedIdGen {
90
91 fn generate_message_id(&self) -> MessageId {
92 let msg_id = format!("{unique}.{hash:x}@{domain}",
93 unique=self.part_unique_in_domain,
94 hash=gen_next_program_unique_number(),
95 domain=self.domain);
96 MessageId::from_unchecked(msg_id)
97 }
98
99 fn generate_content_id(&self) -> ContentId {
100 self.generate_message_id().into()
101 }
102
103}
104
105#[cfg(test)]
106mod test {
107
108 mod HashedIdGen {
109 #![allow(non_snake_case)]
110
111 use std::sync::Arc;
112 use std::collections::HashSet;
113 use soft_ascii_string::SoftAsciiString;
114 use headers::header_components::Domain;
115 use headers::HeaderTryFrom;
116
117 #[allow(unused_imports)]
119 use ::context::MailIdGenComponent;
120 use super::super::HashedIdGen;
121
122 fn setup() -> Arc<HashedIdGen> {
123 let unique_part = SoftAsciiString::from_unchecked("bfr7tz4");
124 let domain = Domain::try_from("fooblabar.test").unwrap();
125 Arc::new(HashedIdGen::new(domain, unique_part).unwrap())
126 }
127
128 mod get_message_id {
129 use super::*;
130
131 #[test]
132 fn should_always_return_a_new_id() {
133 let id_gen = setup();
134 let mut cids = HashSet::new();
135 for _ in 0..20 {
136 assert!(cids.insert(id_gen.generate_message_id()))
137 }
138 }
139 }
140
141 mod generate_content_id {
142 use super::*;
143
144 #[test]
145 fn should_always_return_a_new_id() {
146 let id_gen = setup();
147 let mut cids = HashSet::new();
148 for _ in 0..20 {
149 assert!(cids.insert(id_gen.generate_content_id()))
150 }
151 }
152 }
153 }
154}