aboutsummaryrefslogblamecommitdiff
path: root/src/uidindex.rs
blob: d8abd744955429b2d7b07f419306541b4dc6bf2f (plain) (tree)
1
2
3
4
5
6
7
8
9
                                           



                                                                         

                               
                       
 
                                                               

                                    



                                                                    
                                                                   
                                   

                



                                                                         
                                                                                           
                     

                                                       
 




                                               


                                     

 
                                               
                     



                                           



               

                                                                                 


               

                                                               


               

                                                                                 


               

                                                                                 
     























                                                                                




                           


                                          












                                              

                                                            


                                                              
 
                                                   

                                              





                                                                       
 



                                                           


                                              
                                           



                                                                                             

                                     
                                                      








                                                                               

                 
                                                     



                                                                               

                 




           























                                                                  



                                                     
                                                












                                                                                  


                                          




                                         


                                                                 










                                                                    

                                                        












                                          
                                          
















                                                                                 
                              






                                                                    

                  
use im::{HashMap, HashSet, OrdMap, OrdSet};
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};

use crate::bayou::*;

pub type ImapUid = u32;
pub type ImapUidvalidity = u32;
pub type Flag = String;

/// Mail Identifier (MailIdent) are composed of two components:
/// - a process identifier, 128 bits
/// - a sequence number, 64 bits
/// They are not part of the protocol but an internal representation
/// required by Mailrage/Aerogramme.
/// Their main property is to be unique without having to rely
/// on synchronization between IMAP processes.
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Debug)]
pub struct MailIdent(pub [u8; 24]);

#[derive(Clone)]
/// A UidIndex handles the mutable part of a mailbox
/// It is built by running the event log on it
/// Each applied log generates a new UidIndex by cloning the previous one
/// and applying the event. This is why we use immutable datastructures
/// that are optimized for cloning: they clone underlying values only if they are modified.
pub struct UidIndex {
    // Source of trust
    pub table: OrdMap<MailIdent, (ImapUid, Vec<Flag>)>,

    // Indexes optimized for queries
    pub idx_by_uid: OrdMap<ImapUid, MailIdent>,
    pub idx_by_flag: FlagIndex,

    // Counters
    pub uidvalidity: ImapUidvalidity,
    pub uidnext: ImapUid,
    pub internalseq: ImapUid,
}

#[derive(Clone, Serialize, Deserialize, Debug)]
pub enum UidIndexOp {
    MailAdd(MailIdent, ImapUid, Vec<Flag>),
    MailDel(MailIdent),
    FlagAdd(MailIdent, Vec<Flag>),
    FlagDel(MailIdent, Vec<Flag>),
}

impl UidIndex {
    #[must_use]
    pub fn op_mail_add(&self, ident: MailIdent, flags: Vec<Flag>) -> UidIndexOp {
        UidIndexOp::MailAdd(ident, self.internalseq, flags)
    }

    #[must_use]
    pub fn op_mail_del(&self, ident: MailIdent) -> UidIndexOp {
        UidIndexOp::MailDel(ident)
    }

    #[must_use]
    pub fn op_flag_add(&self, ident: MailIdent, flags: Vec<Flag>) -> UidIndexOp {
        UidIndexOp::FlagAdd(ident, flags)
    }

    #[must_use]
    pub fn op_flag_del(&self, ident: MailIdent, flags: Vec<Flag>) -> UidIndexOp {
        UidIndexOp::FlagDel(ident, flags)
    }

    fn add_email(&mut self, ident: MailIdent, uid: ImapUid, flags: &Vec<Flag>) {
        // Insert the email in our table
        self.table.insert(ident, (uid, flags.clone()));

        // Update the indexes/caches
        self.idx_by_uid.insert(uid, ident);
        self.idx_by_flag.insert(uid, flags);
    }

    fn rm_email(&mut self, ident: &MailIdent) {
        // We do nothing if the mail does not exist
        let (uid, flags) = match self.table.get(ident) {
            Some(v) => v,
            None => return,
        };

        // Delete all cache entries
        self.idx_by_uid.remove(uid);
        self.idx_by_flag.remove(*uid, flags);

        // Remove from source of trust
        self.table.remove(ident);
    }
}

impl Default for UidIndex {
    fn default() -> Self {
        Self {
            table: OrdMap::new(),
            idx_by_uid: OrdMap::new(),
            idx_by_flag: FlagIndex::new(),
            uidvalidity: 1,
            uidnext: 1,
            internalseq: 1,
        }
    }
}

impl BayouState for UidIndex {
    type Op = UidIndexOp;

    fn apply(&self, op: &UidIndexOp) -> Self {
        let mut new = self.clone();
        match op {
            UidIndexOp::MailAdd(ident, uid, flags) => {
                // Change UIDValidity if there is a conflict
                if *uid < new.internalseq {
                    new.uidvalidity += new.internalseq - *uid;
                }

                // Assign the real uid of the email
                let new_uid = new.internalseq;

                // Delete the previous entry if any.
                // Our proof has no assumption on `ident` uniqueness,
                // so we must handle this case even it is very unlikely
                // In this case, we overwrite the email.
                // Note: assigning a new UID is mandatory.
                new.rm_email(ident);

                // We record our email and update ou caches
                new.add_email(*ident, *uid, flags);

                // Update counters
                new.internalseq += 1;
                new.uidnext = new.internalseq;
            }
            UidIndexOp::MailDel(ident) => {
                // If the email is known locally, we remove its references in all our indexes
                new.rm_email(ident);

                // We update the counter
                new.internalseq += 1;
            }
            UidIndexOp::FlagAdd(ident, new_flags) => {
                if let Some((uid, existing_flags)) = new.table.get_mut(ident) {
                    // Add flags to the source of trust and the cache
                    let mut to_add: Vec<Flag> = new_flags
                        .iter()
                        .filter(|f| !existing_flags.contains(f))
                        .cloned()
                        .collect();
                    new.idx_by_flag.insert(*uid, &to_add);
                    existing_flags.append(&mut to_add);
                }
            }
            UidIndexOp::FlagDel(ident, rm_flags) => {
                if let Some((uid, existing_flags)) = new.table.get_mut(ident) {
                    // Remove flags from the source of trust and the cache
                    existing_flags.retain(|x| !rm_flags.contains(x));
                    new.idx_by_flag.remove(*uid, rm_flags);
                }
            }
        }
        new
    }
}

// ---- FlagIndex implementation ----
#[derive(Clone)]
pub struct FlagIndex(HashMap<Flag, OrdSet<ImapUid>>);

impl FlagIndex {
    fn new() -> Self {
        Self(HashMap::new())
    }
    fn insert(&mut self, uid: ImapUid, flags: &Vec<Flag>) {
        flags.iter().for_each(|flag| {
            self.0
                .entry(flag.clone())
                .or_insert(OrdSet::new())
                .insert(uid);
        });
    }

    fn remove(&mut self, uid: ImapUid, flags: &Vec<Flag>) -> () {
        flags.iter().for_each(|flag| {
            self.0.get_mut(flag).and_then(|set| set.remove(&uid));
        });
    }
}

// ---- CUSTOM SERIALIZATION AND DESERIALIZATION ----

#[derive(Serialize, Deserialize)]
struct UidIndexSerializedRepr {
    mails: Vec<(ImapUid, MailIdent, Vec<Flag>)>,
    uidvalidity: ImapUidvalidity,
    uidnext: ImapUid,
    internalseq: ImapUid,
}

impl<'de> Deserialize<'de> for UidIndex {
    fn deserialize<D>(d: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let val: UidIndexSerializedRepr = UidIndexSerializedRepr::deserialize(d)?;

        let mut uidindex = UidIndex {
            table: OrdMap::new(),
            idx_by_uid: OrdMap::new(),
            idx_by_flag: FlagIndex::new(),
            uidvalidity: val.uidvalidity,
            uidnext: val.uidnext,
            internalseq: val.internalseq,
        };

        val.mails
            .iter()
            .for_each(|(u, i, f)| uidindex.add_email(*i, *u, f));

        Ok(uidindex)
    }
}

impl Serialize for UidIndex {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut mails = vec![];
        for (ident, (uid, flags)) in self.table.iter() {
            mails.push((*uid, *ident, flags.clone()));
        }

        let val = UidIndexSerializedRepr {
            mails,
            uidvalidity: self.uidvalidity,
            uidnext: self.uidnext,
            internalseq: self.internalseq,
        };

        val.serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for MailIdent {
    fn deserialize<D>(d: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let v = String::deserialize(d)?;
        let bytes = hex::decode(v).map_err(|_| D::Error::custom("invalid hex"))?;

        if bytes.len() != 24 {
            return Err(D::Error::custom("bad length"));
        }

        let mut tmp = [0u8; 24];
        tmp[..].copy_from_slice(&bytes);
        Ok(Self(tmp))
    }
}

impl Serialize for MailIdent {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&hex::encode(self.0))
    }
}

// ---- TESTS ----