Rename MailIdent to UniqueIdent and use those to identify mailboxes

This commit is contained in:
Alex 2022-06-29 15:52:09 +02:00
parent b95028f89e
commit d737e33b5a
Signed by: lx
GPG Key ID: 0E496D15096376BE
7 changed files with 76 additions and 74 deletions

View File

@ -8,7 +8,7 @@ use imap_codec::types::flag::Flag;
use imap_codec::types::mailbox::{ListMailbox, Mailbox as MailboxCodec}; use imap_codec::types::mailbox::{ListMailbox, Mailbox as MailboxCodec};
use imap_codec::types::response::{Code, Data, Status}; use imap_codec::types::response::{Code, Data, Status};
use crate::mail::mailbox::{Mailbox, Summary}; use crate::mail::mailbox::Mailbox;
use crate::mail::uidindex::UidIndex; use crate::mail::uidindex::UidIndex;
const DEFAULT_FLAGS: [Flag; 5] = [ const DEFAULT_FLAGS: [Flag; 5] = [
@ -35,6 +35,7 @@ impl MailboxView {
/// Creates a new IMAP view into a mailbox. /// Creates a new IMAP view into a mailbox.
/// Generates the necessary IMAP messages so that the client /// Generates the necessary IMAP messages so that the client
/// has a satisfactory summary of the current mailbox's state. /// has a satisfactory summary of the current mailbox's state.
/// These are the messages that are sent in response to a SELECT command.
pub async fn new(mailbox: Arc<Mailbox>) -> Result<(Self, Vec<Body>)> { pub async fn new(mailbox: Arc<Mailbox>) -> Result<(Self, Vec<Body>)> {
let state = mailbox.current_uid_index().await; let state = mailbox.current_uid_index().await;
@ -140,7 +141,11 @@ impl MailboxView {
}) })
.flatten() .flatten()
.collect(); .collect();
flags.extend_from_slice(&DEFAULT_FLAGS); for f in DEFAULT_FLAGS.iter() {
if !flags.contains(f) {
flags.push(f.clone());
}
}
let mut ret = vec![Body::Data(Data::Flags(flags.clone()))]; let mut ret = vec![Body::Data(Data::Flags(flags.clone()))];
flags.push(Flag::Permanent); flags.push(Flag::Permanent);

View File

@ -20,7 +20,7 @@ use smtp_server::{reply, Config, ConnectionMetadata, Decision, MailMetadata};
use crate::config::*; use crate::config::*;
use crate::cryptoblob::*; use crate::cryptoblob::*;
use crate::login::*; use crate::login::*;
use crate::mail::mail_ident::*; use crate::mail::unique_ident::*;
pub struct LmtpServer { pub struct LmtpServer {
bind_addr: SocketAddr, bind_addr: SocketAddr,

View File

@ -8,53 +8,40 @@ use tokio::sync::RwLock;
use crate::bayou::Bayou; use crate::bayou::Bayou;
use crate::cryptoblob::Key; use crate::cryptoblob::Key;
use crate::login::Credentials; use crate::login::Credentials;
use crate::mail::mail_ident::*;
use crate::mail::uidindex::*; use crate::mail::uidindex::*;
use crate::mail::unique_ident::*;
use crate::mail::IMF; use crate::mail::IMF;
pub struct Summary { pub struct Mailbox {
pub validity: ImapUidvalidity, id: UniqueIdent,
pub next: ImapUid, mbox: RwLock<MailboxInternal>,
pub exists: u32,
pub recent: u32,
pub flags: Vec<String>,
pub unseen: Option<ImapUid>,
} }
impl std::fmt::Display for Summary {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"uidvalidity: {}, uidnext: {}, exists: {}",
self.validity, self.next, self.exists
)
}
}
pub struct Mailbox(RwLock<MailboxInternal>);
impl Mailbox { impl Mailbox {
pub(super) async fn open(creds: &Credentials, name: &str) -> Result<Self> { pub(super) async fn open(creds: &Credentials, id: UniqueIdent) -> Result<Self> {
let index_path = format!("index/{}", name); let index_path = format!("index/{}", id);
let mail_path = format!("mail/{}", name); let mail_path = format!("mail/{}", id);
let mut uid_index = Bayou::<UidIndex>::new(creds, index_path)?; let mut uid_index = Bayou::<UidIndex>::new(creds, index_path)?;
uid_index.sync().await?; uid_index.sync().await?;
Ok(Self(RwLock::new(MailboxInternal { let mbox = RwLock::new(MailboxInternal {
id,
bucket: creds.bucket().to_string(), bucket: creds.bucket().to_string(),
key: creds.keys.master.clone(), encryption_key: creds.keys.master.clone(),
k2v: creds.k2v_client()?, k2v: creds.k2v_client()?,
s3: creds.s3_client()?, s3: creds.s3_client()?,
uid_index, uid_index,
mail_path, mail_path,
}))) });
Ok(Self { id, mbox })
} }
/// Get a clone of the current UID Index of this mailbox /// Get a clone of the current UID Index of this mailbox
/// (cloning is cheap so don't hesitate to use this) /// (cloning is cheap so don't hesitate to use this)
pub async fn current_uid_index(&self) -> UidIndex { pub async fn current_uid_index(&self) -> UidIndex {
self.0.read().await.uid_index.state().clone() self.mbox.read().await.uid_index.state().clone()
} }
/// Insert an email in the mailbox /// Insert an email in the mailbox
@ -91,14 +78,15 @@ impl Mailbox {
// Non standard but common flags: // Non standard but common flags:
// https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml // https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml
struct MailboxInternal { struct MailboxInternal {
id: UniqueIdent,
bucket: String, bucket: String,
key: Key, mail_path: String,
encryption_key: Key,
k2v: K2vClient, k2v: K2vClient,
s3: S3Client, s3: S3Client,
uid_index: Bayou<UidIndex>, uid_index: Bayou<UidIndex>,
mail_path: String,
} }
impl MailboxInternal { impl MailboxInternal {

View File

@ -1,6 +1,6 @@
pub mod mail_ident;
pub mod mailbox; pub mod mailbox;
pub mod uidindex; pub mod uidindex;
pub mod unique_ident;
pub mod user; pub mod user;
use std::convert::TryFrom; use std::convert::TryFrom;
@ -12,8 +12,8 @@ use rusoto_s3::S3Client;
use crate::bayou::Bayou; use crate::bayou::Bayou;
use crate::cryptoblob::Key; use crate::cryptoblob::Key;
use crate::login::Credentials; use crate::login::Credentials;
use crate::mail::mail_ident::*;
use crate::mail::uidindex::*; use crate::mail::uidindex::*;
use crate::mail::unique_ident::*;
// Internet Message Format // Internet Message Format
// aka RFC 822 - RFC 2822 - RFC 5322 // aka RFC 822 - RFC 2822 - RFC 5322

View File

@ -4,7 +4,7 @@ use im::{HashMap, OrdMap, OrdSet};
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::bayou::*; use crate::bayou::*;
use crate::mail::mail_ident::MailIdent; use crate::mail::unique_ident::UniqueIdent;
pub type ImapUid = NonZeroU32; pub type ImapUid = NonZeroU32;
pub type ImapUidvalidity = NonZeroU32; pub type ImapUidvalidity = NonZeroU32;
@ -18,10 +18,10 @@ pub type Flag = String;
#[derive(Clone)] #[derive(Clone)]
pub struct UidIndex { pub struct UidIndex {
// Source of trust // Source of trust
pub table: OrdMap<MailIdent, (ImapUid, Vec<Flag>)>, pub table: OrdMap<UniqueIdent, (ImapUid, Vec<Flag>)>,
// Indexes optimized for queries // Indexes optimized for queries
pub idx_by_uid: OrdMap<ImapUid, MailIdent>, pub idx_by_uid: OrdMap<ImapUid, UniqueIdent>,
pub idx_by_flag: FlagIndex, pub idx_by_flag: FlagIndex,
// Counters // Counters
@ -32,36 +32,36 @@ pub struct UidIndex {
#[derive(Clone, Serialize, Deserialize, Debug)] #[derive(Clone, Serialize, Deserialize, Debug)]
pub enum UidIndexOp { pub enum UidIndexOp {
MailAdd(MailIdent, ImapUid, Vec<Flag>), MailAdd(UniqueIdent, ImapUid, Vec<Flag>),
MailDel(MailIdent), MailDel(UniqueIdent),
FlagAdd(MailIdent, Vec<Flag>), FlagAdd(UniqueIdent, Vec<Flag>),
FlagDel(MailIdent, Vec<Flag>), FlagDel(UniqueIdent, Vec<Flag>),
} }
impl UidIndex { impl UidIndex {
#[must_use] #[must_use]
pub fn op_mail_add(&self, ident: MailIdent, flags: Vec<Flag>) -> UidIndexOp { pub fn op_mail_add(&self, ident: UniqueIdent, flags: Vec<Flag>) -> UidIndexOp {
UidIndexOp::MailAdd(ident, self.internalseq, flags) UidIndexOp::MailAdd(ident, self.internalseq, flags)
} }
#[must_use] #[must_use]
pub fn op_mail_del(&self, ident: MailIdent) -> UidIndexOp { pub fn op_mail_del(&self, ident: UniqueIdent) -> UidIndexOp {
UidIndexOp::MailDel(ident) UidIndexOp::MailDel(ident)
} }
#[must_use] #[must_use]
pub fn op_flag_add(&self, ident: MailIdent, flags: Vec<Flag>) -> UidIndexOp { pub fn op_flag_add(&self, ident: UniqueIdent, flags: Vec<Flag>) -> UidIndexOp {
UidIndexOp::FlagAdd(ident, flags) UidIndexOp::FlagAdd(ident, flags)
} }
#[must_use] #[must_use]
pub fn op_flag_del(&self, ident: MailIdent, flags: Vec<Flag>) -> UidIndexOp { pub fn op_flag_del(&self, ident: UniqueIdent, flags: Vec<Flag>) -> UidIndexOp {
UidIndexOp::FlagDel(ident, flags) UidIndexOp::FlagDel(ident, flags)
} }
// INTERNAL functions to keep state consistent // INTERNAL functions to keep state consistent
fn reg_email(&mut self, ident: MailIdent, uid: ImapUid, flags: &Vec<Flag>) { fn reg_email(&mut self, ident: UniqueIdent, uid: ImapUid, flags: &Vec<Flag>) {
// Insert the email in our table // Insert the email in our table
self.table.insert(ident, (uid, flags.clone())); self.table.insert(ident, (uid, flags.clone()));
@ -70,7 +70,7 @@ impl UidIndex {
self.idx_by_flag.insert(uid, flags); self.idx_by_flag.insert(uid, flags);
} }
fn unreg_email(&mut self, ident: &MailIdent) { fn unreg_email(&mut self, ident: &UniqueIdent) {
// We do nothing if the mail does not exist // We do nothing if the mail does not exist
let (uid, flags) = match self.table.get(ident) { let (uid, flags) = match self.table.get(ident) {
Some(v) => v, Some(v) => v,
@ -198,7 +198,7 @@ impl FlagIndex {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct UidIndexSerializedRepr { struct UidIndexSerializedRepr {
mails: Vec<(ImapUid, MailIdent, Vec<Flag>)>, mails: Vec<(ImapUid, UniqueIdent, Vec<Flag>)>,
uidvalidity: ImapUidvalidity, uidvalidity: ImapUidvalidity,
uidnext: ImapUid, uidnext: ImapUid,
internalseq: ImapUid, internalseq: ImapUid,
@ -261,7 +261,7 @@ mod tests {
// Add message 1 // Add message 1
{ {
let m = MailIdent([0x01; 24]); let m = UniqueIdent([0x01; 24]);
let f = vec!["\\Recent".to_string(), "\\Archive".to_string()]; let f = vec!["\\Recent".to_string(), "\\Archive".to_string()];
let ev = state.op_mail_add(m, f); let ev = state.op_mail_add(m, f);
state = state.apply(&ev); state = state.apply(&ev);
@ -282,7 +282,7 @@ mod tests {
// Add message 2 // Add message 2
{ {
let m = MailIdent([0x02; 24]); let m = UniqueIdent([0x02; 24]);
let f = vec!["\\Seen".to_string(), "\\Archive".to_string()]; let f = vec!["\\Seen".to_string(), "\\Archive".to_string()];
let ev = state.op_mail_add(m, f); let ev = state.op_mail_add(m, f);
state = state.apply(&ev); state = state.apply(&ev);
@ -293,7 +293,7 @@ mod tests {
// Add flags to message 1 // Add flags to message 1
{ {
let m = MailIdent([0x01; 24]); let m = UniqueIdent([0x01; 24]);
let f = vec!["Important".to_string(), "$cl_1".to_string()]; let f = vec!["Important".to_string(), "$cl_1".to_string()];
let ev = state.op_flag_add(m, f); let ev = state.op_flag_add(m, f);
state = state.apply(&ev); state = state.apply(&ev);
@ -301,7 +301,7 @@ mod tests {
// Delete flags from message 1 // Delete flags from message 1
{ {
let m = MailIdent([0x01; 24]); let m = UniqueIdent([0x01; 24]);
let f = vec!["\\Recent".to_string()]; let f = vec!["\\Recent".to_string()];
let ev = state.op_flag_del(m, f); let ev = state.op_flag_del(m, f);
state = state.apply(&ev); state = state.apply(&ev);
@ -312,7 +312,7 @@ mod tests {
// Delete message 2 // Delete message 2
{ {
let m = MailIdent([0x02; 24]); let m = UniqueIdent([0x02; 24]);
let ev = state.op_mail_del(m); let ev = state.op_mail_del(m);
state = state.apply(&ev); state = state.apply(&ev);
@ -322,7 +322,7 @@ mod tests {
// Add a message 3 concurrent to message 1 (trigger a uid validity change) // Add a message 3 concurrent to message 1 (trigger a uid validity change)
{ {
let m = MailIdent([0x03; 24]); let m = UniqueIdent([0x03; 24]);
let f = vec!["\\Archive".to_string(), "\\Recent".to_string()]; let f = vec!["\\Archive".to_string(), "\\Recent".to_string()];
let ev = UidIndexOp::MailAdd(m, 1, f); let ev = UidIndexOp::MailAdd(m, 1, f);
state = state.apply(&ev); state = state.apply(&ev);
@ -334,7 +334,7 @@ mod tests {
assert!(state.uidvalidity > 1); assert!(state.uidvalidity > 1);
let (last_uid, ident) = state.idx_by_uid.get_max().unwrap(); let (last_uid, ident) = state.idx_by_uid.get_max().unwrap();
assert_eq!(ident, &MailIdent([0x03; 24])); assert_eq!(ident, &UniqueIdent([0x03; 24]));
let archive = state.idx_by_flag.0.get("\\Archive").unwrap(); let archive = state.idx_by_flag.0.get("\\Archive").unwrap();
assert_eq!(archive.len(), 2); assert_eq!(archive.len(), 2);

View File

@ -17,7 +17,7 @@ use crate::time::now_msec;
/// Their main property is to be unique without having to rely /// Their main property is to be unique without having to rely
/// on synchronization between IMAP processes. /// on synchronization between IMAP processes.
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Debug)] #[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Debug)]
pub struct MailIdent(pub [u8; 24]); pub struct UniqueIdent(pub [u8; 24]);
struct IdentGenerator { struct IdentGenerator {
pid: u128, pid: u128,
@ -34,12 +34,12 @@ impl IdentGenerator {
} }
} }
fn gen(&self) -> MailIdent { fn gen(&self) -> UniqueIdent {
let sn = self.sn.fetch_add(1, Ordering::Relaxed); let sn = self.sn.fetch_add(1, Ordering::Relaxed);
let mut res = [0u8; 24]; let mut res = [0u8; 24];
res[0..16].copy_from_slice(&u128::to_be_bytes(self.pid)); res[0..16].copy_from_slice(&u128::to_be_bytes(self.pid));
res[16..24].copy_from_slice(&u64::to_be_bytes(sn)); res[16..24].copy_from_slice(&u64::to_be_bytes(sn));
MailIdent(res) UniqueIdent(res)
} }
} }
@ -47,23 +47,23 @@ lazy_static! {
static ref GENERATOR: IdentGenerator = IdentGenerator::new(); static ref GENERATOR: IdentGenerator = IdentGenerator::new();
} }
pub fn gen_ident() -> MailIdent { pub fn gen_ident() -> UniqueIdent {
GENERATOR.gen() GENERATOR.gen()
} }
// -- serde -- // -- serde --
impl<'de> Deserialize<'de> for MailIdent { impl<'de> Deserialize<'de> for UniqueIdent {
fn deserialize<D>(d: D) -> Result<Self, D::Error> fn deserialize<D>(d: D) -> Result<Self, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
let v = String::deserialize(d)?; let v = String::deserialize(d)?;
MailIdent::from_str(&v).map_err(D::Error::custom) UniqueIdent::from_str(&v).map_err(D::Error::custom)
} }
} }
impl Serialize for MailIdent { impl Serialize for UniqueIdent {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
@ -72,16 +72,16 @@ impl Serialize for MailIdent {
} }
} }
impl ToString for MailIdent { impl std::fmt::Display for UniqueIdent {
fn to_string(&self) -> String { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
hex::encode(self.0) write!(f, "{}", hex::encode(self.0))
} }
} }
impl FromStr for MailIdent { impl FromStr for UniqueIdent {
type Err = &'static str; type Err = &'static str;
fn from_str(s: &str) -> Result<MailIdent, &'static str> { fn from_str(s: &str) -> Result<UniqueIdent, &'static str> {
let bytes = hex::decode(s).map_err(|_| "invalid hex")?; let bytes = hex::decode(s).map_err(|_| "invalid hex")?;
if bytes.len() != 24 { if bytes.len() != 24 {
@ -90,6 +90,6 @@ impl FromStr for MailIdent {
let mut tmp = [0u8; 24]; let mut tmp = [0u8; 24];
tmp[..].copy_from_slice(&bytes); tmp[..].copy_from_slice(&bytes);
Ok(MailIdent(tmp)) Ok(UniqueIdent(tmp))
} }
} }

View File

@ -9,6 +9,7 @@ use rusoto_s3::S3Client;
use crate::login::{Credentials, StorageCredentials}; use crate::login::{Credentials, StorageCredentials};
use crate::mail::mailbox::Mailbox; use crate::mail::mailbox::Mailbox;
use crate::mail::unique_ident::UniqueIdent;
pub struct User { pub struct User {
pub username: String, pub username: String,
@ -36,21 +37,29 @@ impl User {
/// Opens an existing mailbox given its IMAP name. /// Opens an existing mailbox given its IMAP name.
pub async fn open_mailbox(&self, name: &str) -> Result<Option<Arc<Mailbox>>> { pub async fn open_mailbox(&self, name: &str) -> Result<Option<Arc<Mailbox>>> {
// TODO: handle mailbox names, mappings, renaming, etc
let id = match name {
"INBOX" => UniqueIdent([0u8; 24]),
_ => panic!("Only INBOX exists for now"),
};
let cache_key = (self.creds.storage.clone(), id);
{ {
let cache = MAILBOX_CACHE.cache.lock().unwrap(); let cache = MAILBOX_CACHE.cache.lock().unwrap();
if let Some(mb) = cache.get(&self.creds.storage).and_then(Weak::upgrade) { if let Some(mb) = cache.get(&cache_key).and_then(Weak::upgrade) {
return Ok(Some(mb)); return Ok(Some(mb));
} }
} }
let mb = Arc::new(Mailbox::open(&self.creds, name).await?); let mb = Arc::new(Mailbox::open(&self.creds, id).await?);
let mut cache = MAILBOX_CACHE.cache.lock().unwrap(); let mut cache = MAILBOX_CACHE.cache.lock().unwrap();
if let Some(concurrent_mb) = cache.get(&self.creds.storage).and_then(Weak::upgrade) { if let Some(concurrent_mb) = cache.get(&cache_key).and_then(Weak::upgrade) {
drop(mb); // we worked for nothing but at least we didn't starve someone else drop(mb); // we worked for nothing but at least we didn't starve someone else
Ok(Some(concurrent_mb)) Ok(Some(concurrent_mb))
} else { } else {
cache.insert(self.creds.storage.clone(), Arc::downgrade(&mb)); cache.insert(cache_key, Arc::downgrade(&mb));
Ok(Some(mb)) Ok(Some(mb))
} }
} }
@ -74,7 +83,7 @@ impl User {
// ---- Mailbox cache ---- // ---- Mailbox cache ----
struct MailboxCache { struct MailboxCache {
cache: std::sync::Mutex<HashMap<StorageCredentials, Weak<Mailbox>>>, cache: std::sync::Mutex<HashMap<(StorageCredentials, UniqueIdent), Weak<Mailbox>>>,
} }
impl MailboxCache { impl MailboxCache {