use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::{
io::{self, Cursor, Read, Write},
marker::PhantomData,
rc::Rc,
};
use super::{read_write::*, *};
use crate::{
messaging::store::{AnsiStore, UnicodeStore},
ndb::{
block::{DataTree, IntermediateTreeBlock},
block_id::BlockId,
header::Header,
node_id::{NodeId, NID_SEARCH_MANAGEMENT_QUEUE},
page::{BTreePage, NodeBTreeEntry, RootBTree},
read_write::*,
root::Root,
},
AnsiPstFile, PstFile, UnicodePstFile,
};
#[repr(u16)]
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
pub enum SearchUpdateFlags {
#[default]
None = 0x0000,
PriorityLow = 0x0001,
PriorityHigh = 0x0002,
SearchRestart = 0x0004,
NameChanged = 0x0008,
MoveOutToIn = 0x0010,
MoveInToIn = 0x0020,
MoveInToOut = 0x0040,
MoveOutToOut = 0x0080,
SpamCheckServer = 0x0100,
SetDelegateRootName = 0x0200,
SearchDone = 0x0400,
DomainChecked = 0x8000,
}
#[repr(u16)]
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
pub enum SearchUpdateType {
#[default]
Null = 0x0000,
MessageAdded = 0x0001,
MessageModified = 0x0002,
MessageDeleted = 0x0003,
MessageMoved = 0x0004,
FolderAdded = 0x0005,
FolderModified = 0x0006,
FolderDeleted = 0x0007,
FolderMoved = 0x0008,
SearchFolderAdded = 0x0009,
SearchFolderModified = 0x000A,
SearchFolderDeleted = 0x000B,
MessageRowModified = 0x000C,
MessageSpam = 0x000D,
IndexedMessageDeleted = 0x000E,
MessageIndexed = 0x000F,
}
impl TryFrom<u16> for SearchUpdateType {
type Error = MessagingError;
fn try_from(value: u16) -> Result<Self, Self::Error> {
match value {
0x0000 => Ok(Self::Null),
0x0001 => Ok(Self::MessageAdded),
0x0002 => Ok(Self::MessageModified),
0x0003 => Ok(Self::MessageDeleted),
0x0004 => Ok(Self::MessageMoved),
0x0005 => Ok(Self::FolderAdded),
0x0006 => Ok(Self::FolderModified),
0x0007 => Ok(Self::FolderDeleted),
0x0008 => Ok(Self::FolderMoved),
0x0009 => Ok(Self::SearchFolderAdded),
0x000A => Ok(Self::SearchFolderModified),
0x000B => Ok(Self::SearchFolderDeleted),
0x000C => Ok(Self::MessageRowModified),
0x000D => Ok(Self::MessageSpam),
0x000E => Ok(Self::IndexedMessageDeleted),
0x000F => Ok(Self::MessageIndexed),
invalid => Err(MessagingError::InvalidSearchUpdateType(invalid)),
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum SearchUpdateData {
MessageAdded { parent: NodeId, message: NodeId },
MessageModified { parent: NodeId, message: NodeId },
MessageDeleted { parent: NodeId, message: NodeId },
MessageMoved {
new_parent: NodeId,
message: NodeId,
old_parent: NodeId,
},
FolderAdded {
parent: NodeId,
folder: NodeId,
reserved1: u32,
reserved2: u32,
},
FolderModified { folder: NodeId, reserved: u32 },
FolderDeleted { folder: NodeId, reserved: u32 },
FolderMoved {
parent: NodeId,
folder: NodeId,
reserved1: u32,
reserved2: u32,
},
SearchFolderAdded { search_folder: NodeId },
SearchFolderModified {
search_folder: NodeId,
reserved: u32,
},
SearchFolderDeleted { search_folder: NodeId },
MessageRowModified { parent: NodeId, message: NodeId },
MessageSpam { parent: NodeId, message: NodeId },
IndexedMessageDeleted { parent: NodeId, message: NodeId },
MessageIndexed { message: NodeId },
}
impl From<&SearchUpdateData> for SearchUpdateType {
fn from(value: &SearchUpdateData) -> Self {
match value {
SearchUpdateData::MessageAdded { .. } => Self::MessageAdded,
SearchUpdateData::MessageModified { .. } => Self::MessageModified,
SearchUpdateData::MessageDeleted { .. } => Self::MessageDeleted,
SearchUpdateData::MessageMoved { .. } => Self::MessageMoved,
SearchUpdateData::FolderAdded { .. } => Self::FolderAdded,
SearchUpdateData::FolderModified { .. } => Self::FolderModified,
SearchUpdateData::FolderDeleted { .. } => Self::FolderDeleted,
SearchUpdateData::FolderMoved { .. } => Self::FolderMoved,
SearchUpdateData::SearchFolderAdded { .. } => Self::SearchFolderAdded,
SearchUpdateData::SearchFolderModified { .. } => Self::SearchFolderModified,
SearchUpdateData::SearchFolderDeleted { .. } => Self::SearchFolderDeleted,
SearchUpdateData::MessageRowModified { .. } => Self::MessageRowModified,
SearchUpdateData::MessageSpam { .. } => Self::MessageSpam,
SearchUpdateData::IndexedMessageDeleted { .. } => Self::IndexedMessageDeleted,
SearchUpdateData::MessageIndexed { .. } => Self::MessageIndexed,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct SearchUpdate {
flags: u16,
data: Option<SearchUpdateData>,
}
impl SearchUpdate {
pub fn flags(&self) -> u16 {
self.flags
}
pub fn data(&self) -> Option<&SearchUpdateData> {
self.data.as_ref()
}
}
impl SearchReadWrite for SearchUpdate {
fn read(f: &mut dyn Read) -> io::Result<Self> {
let flags = f.read_u16::<LittleEndian>()?;
let data_type = SearchUpdateType::try_from(f.read_u16::<LittleEndian>()?)?;
let mut buffer = [0u8; 16];
f.read_exact(&mut buffer)?;
let mut cursor = Cursor::new(buffer);
let data = match data_type {
SearchUpdateType::Null => None,
SearchUpdateType::MessageAdded => {
let parent = NodeId::from(cursor.read_u32::<LittleEndian>()?);
let message = NodeId::from(cursor.read_u32::<LittleEndian>()?);
Some(SearchUpdateData::MessageAdded { parent, message })
}
SearchUpdateType::MessageModified => {
let parent = NodeId::from(cursor.read_u32::<LittleEndian>()?);
let message = NodeId::from(cursor.read_u32::<LittleEndian>()?);
Some(SearchUpdateData::MessageModified { parent, message })
}
SearchUpdateType::MessageDeleted => {
let parent = NodeId::from(cursor.read_u32::<LittleEndian>()?);
let message = NodeId::from(cursor.read_u32::<LittleEndian>()?);
Some(SearchUpdateData::MessageDeleted { parent, message })
}
SearchUpdateType::MessageMoved => {
let new_parent = NodeId::from(cursor.read_u32::<LittleEndian>()?);
let message = NodeId::from(cursor.read_u32::<LittleEndian>()?);
let old_parent = NodeId::from(cursor.read_u32::<LittleEndian>()?);
Some(SearchUpdateData::MessageMoved {
new_parent,
message,
old_parent,
})
}
SearchUpdateType::FolderAdded => {
let parent = NodeId::from(cursor.read_u32::<LittleEndian>()?);
let folder = NodeId::from(cursor.read_u32::<LittleEndian>()?);
let reserved1 = cursor.read_u32::<LittleEndian>()?;
let reserved2 = cursor.read_u32::<LittleEndian>()?;
Some(SearchUpdateData::FolderAdded {
parent,
folder,
reserved1,
reserved2,
})
}
SearchUpdateType::FolderModified => {
let folder = NodeId::from(cursor.read_u32::<LittleEndian>()?);
let reserved = cursor.read_u32::<LittleEndian>()?;
Some(SearchUpdateData::FolderModified { folder, reserved })
}
SearchUpdateType::FolderDeleted => {
let folder = NodeId::from(cursor.read_u32::<LittleEndian>()?);
let reserved = cursor.read_u32::<LittleEndian>()?;
Some(SearchUpdateData::FolderDeleted { folder, reserved })
}
SearchUpdateType::FolderMoved => {
let parent = NodeId::from(cursor.read_u32::<LittleEndian>()?);
let folder = NodeId::from(cursor.read_u32::<LittleEndian>()?);
let reserved1 = cursor.read_u32::<LittleEndian>()?;
let reserved2 = cursor.read_u32::<LittleEndian>()?;
Some(SearchUpdateData::FolderMoved {
parent,
folder,
reserved1,
reserved2,
})
}
SearchUpdateType::SearchFolderAdded => {
let search_folder = NodeId::from(cursor.read_u32::<LittleEndian>()?);
Some(SearchUpdateData::SearchFolderAdded { search_folder })
}
SearchUpdateType::SearchFolderModified => {
let search_folder = NodeId::from(cursor.read_u32::<LittleEndian>()?);
let reserved = cursor.read_u32::<LittleEndian>()?;
Some(SearchUpdateData::SearchFolderModified {
search_folder,
reserved,
})
}
SearchUpdateType::SearchFolderDeleted => {
let search_folder = NodeId::from(cursor.read_u32::<LittleEndian>()?);
Some(SearchUpdateData::SearchFolderDeleted { search_folder })
}
SearchUpdateType::MessageRowModified => {
let parent = NodeId::from(cursor.read_u32::<LittleEndian>()?);
let message = NodeId::from(cursor.read_u32::<LittleEndian>()?);
Some(SearchUpdateData::MessageRowModified { parent, message })
}
SearchUpdateType::MessageSpam => {
let parent = NodeId::from(cursor.read_u32::<LittleEndian>()?);
let message = NodeId::from(cursor.read_u32::<LittleEndian>()?);
Some(SearchUpdateData::MessageSpam { parent, message })
}
SearchUpdateType::IndexedMessageDeleted => {
let parent = NodeId::from(cursor.read_u32::<LittleEndian>()?);
let message = NodeId::from(cursor.read_u32::<LittleEndian>()?);
Some(SearchUpdateData::IndexedMessageDeleted { parent, message })
}
SearchUpdateType::MessageIndexed => {
let message = NodeId::from(cursor.read_u32::<LittleEndian>()?);
Some(SearchUpdateData::MessageIndexed { message })
}
};
Ok(Self { flags, data })
}
fn write(&self, f: &mut dyn Write) -> io::Result<()> {
f.write_u16::<LittleEndian>(self.flags)?;
let data_type = self
.data
.as_ref()
.map(SearchUpdateType::from)
.unwrap_or(SearchUpdateType::Null);
f.write_u16::<LittleEndian>(data_type as u16)?;
let mut cursor = Cursor::new([0u8; 16]);
if let Some(data) = &self.data {
match *data {
SearchUpdateData::MessageAdded { parent, message } => {
cursor.write_u32::<LittleEndian>(parent.into())?;
cursor.write_u32::<LittleEndian>(message.into())?;
}
SearchUpdateData::MessageModified { parent, message } => {
cursor.write_u32::<LittleEndian>(parent.into())?;
cursor.write_u32::<LittleEndian>(message.into())?;
}
SearchUpdateData::MessageDeleted { parent, message } => {
cursor.write_u32::<LittleEndian>(parent.into())?;
cursor.write_u32::<LittleEndian>(message.into())?;
}
SearchUpdateData::MessageMoved {
new_parent,
message,
old_parent,
} => {
cursor.write_u32::<LittleEndian>(new_parent.into())?;
cursor.write_u32::<LittleEndian>(message.into())?;
cursor.write_u32::<LittleEndian>(old_parent.into())?;
}
SearchUpdateData::FolderAdded {
parent,
folder,
reserved1,
reserved2,
} => {
cursor.write_u32::<LittleEndian>(parent.into())?;
cursor.write_u32::<LittleEndian>(folder.into())?;
cursor.write_u32::<LittleEndian>(reserved1)?;
cursor.write_u32::<LittleEndian>(reserved2)?;
}
SearchUpdateData::FolderModified { folder, reserved } => {
cursor.write_u32::<LittleEndian>(folder.into())?;
cursor.write_u32::<LittleEndian>(reserved)?;
}
SearchUpdateData::FolderDeleted { folder, reserved } => {
cursor.write_u32::<LittleEndian>(folder.into())?;
cursor.write_u32::<LittleEndian>(reserved)?;
}
SearchUpdateData::FolderMoved {
parent,
folder,
reserved1,
reserved2,
} => {
cursor.write_u32::<LittleEndian>(parent.into())?;
cursor.write_u32::<LittleEndian>(folder.into())?;
cursor.write_u32::<LittleEndian>(reserved1)?;
cursor.write_u32::<LittleEndian>(reserved2)?;
}
SearchUpdateData::SearchFolderAdded { search_folder } => {
cursor.write_u32::<LittleEndian>(search_folder.into())?;
}
SearchUpdateData::SearchFolderModified {
search_folder,
reserved,
} => {
cursor.write_u32::<LittleEndian>(search_folder.into())?;
cursor.write_u32::<LittleEndian>(reserved)?;
}
SearchUpdateData::SearchFolderDeleted { search_folder } => {
cursor.write_u32::<LittleEndian>(search_folder.into())?;
}
SearchUpdateData::MessageRowModified { parent, message } => {
cursor.write_u32::<LittleEndian>(parent.into())?;
cursor.write_u32::<LittleEndian>(message.into())?;
}
SearchUpdateData::MessageSpam { parent, message } => {
cursor.write_u32::<LittleEndian>(parent.into())?;
cursor.write_u32::<LittleEndian>(message.into())?;
}
SearchUpdateData::IndexedMessageDeleted { parent, message } => {
cursor.write_u32::<LittleEndian>(parent.into())?;
cursor.write_u32::<LittleEndian>(message.into())?;
}
SearchUpdateData::MessageIndexed { message } => {
cursor.write_u32::<LittleEndian>(message.into())?;
}
};
}
let data = cursor.into_inner();
f.write_all(&data)
}
}
const SEARCH_UPDATE_SIZE: u32 = 20;
pub trait SearchUpdateQueue {
fn updates(&self) -> &[SearchUpdate];
}
struct SearchUpdateQueueInner<Pst>
where
Pst: PstFile,
{
updates: Vec<SearchUpdate>,
_phantom: PhantomData<Pst>,
}
impl<Pst> SearchUpdateQueueInner<Pst>
where
Pst: PstFile,
<Pst as PstFile>::BTreeKey: BTreePageKeyReadWrite,
u64: From<Pst::BTreeKey>,
<Pst as PstFile>::NodeBTreeEntry: NodeBTreeEntryReadWrite,
<Pst as PstFile>::NodeBTree: RootBTreeReadWrite,
<<Pst as PstFile>::NodeBTree as RootBTree>::IntermediatePage:
RootBTreeIntermediatePageReadWrite<
Pst,
<Pst as PstFile>::NodeBTreeEntry,
<<Pst as PstFile>::NodeBTree as RootBTree>::LeafPage,
>,
<<<Pst as PstFile>::NodeBTree as RootBTree>::IntermediatePage as BTreePage>::Entry:
BTreePageEntryReadWrite,
<<Pst as PstFile>::NodeBTree as RootBTree>::LeafPage: RootBTreeLeafPageReadWrite<Pst>,
<Pst as PstFile>::BlockBTreeEntry: BlockBTreeEntryReadWrite,
<Pst as PstFile>::BlockBTree: RootBTreeReadWrite,
<<Pst as PstFile>::BlockBTree as RootBTree>::Entry: BTreeEntryReadWrite,
<<Pst as PstFile>::BlockBTree as RootBTree>::IntermediatePage:
RootBTreeIntermediatePageReadWrite<
Pst,
<<Pst as PstFile>::BlockBTree as RootBTree>::Entry,
<<Pst as PstFile>::BlockBTree as RootBTree>::LeafPage,
>,
<<Pst as PstFile>::BlockBTree as RootBTree>::LeafPage:
RootBTreeLeafPageReadWrite<Pst> + BTreePageReadWrite,
<Pst as PstFile>::BlockTrailer: BlockTrailerReadWrite,
<Pst as PstFile>::BlockTrailer: BlockTrailerReadWrite,
<Pst as PstFile>::DataTreeBlock: IntermediateTreeBlockReadWrite,
<<Pst as PstFile>::DataTreeBlock as IntermediateTreeBlock>::Entry:
IntermediateTreeEntryReadWrite,
<Pst as PstFile>::DataBlock: BlockReadWrite + Clone,
<Pst as PstFile>::Store: StoreReadWrite<Pst>,
{
fn read(store: Rc<Pst::Store>) -> io::Result<Self> {
let pst = store.pst();
let header = pst.header();
let encoding = header.crypt_method();
let root = header.root();
let mut file = pst
.reader()
.lock()
.map_err(|_| MessagingError::FailedToLockFile)?;
let file = &mut *file;
let node_btree = <Pst as PstFile>::NodeBTree::read(file, *root.node_btree())?;
let block_btree = <Pst as PstFile>::BlockBTree::read(file, *root.block_btree())?;
let mut page_cache = Default::default();
let node = node_btree.find_entry(
file,
u32::from(NID_SEARCH_MANAGEMENT_QUEUE).into(),
&mut page_cache,
)?;
let start = node.parent().map(u32::from).unwrap_or_default();
if start % SEARCH_UPDATE_SIZE != 0 {
return Err(MessagingError::InvalidSearchUpdateQueueOffset(start).into());
}
let start = usize::try_from(start)
.map_err(|_| MessagingError::InvalidSearchUpdateQueueOffset(start))?;
let key = node.data().search_key();
if u64::from(key) == 0 {
return Ok(Self {
updates: Default::default(),
_phantom: PhantomData,
});
}
let mut page_cache = Default::default();
let block = block_btree.find_entry(file, key, &mut page_cache)?;
let tree = DataTree::<Pst>::read(file, encoding, &block)?;
let mut data = Vec::new();
let mut block_cache = Default::default();
let size = tree
.reader(
file,
encoding,
&block_btree,
&mut page_cache,
&mut block_cache,
)?
.read_to_end(&mut data)?;
if size % SEARCH_UPDATE_SIZE as usize != 0 {
return Err(MessagingError::InvalidSearchUpdateQueueSize(size).into());
}
let updates = if size > start {
let count = (size - start) / SEARCH_UPDATE_SIZE as usize;
let mut updates = Vec::with_capacity(count);
let mut cursor = Cursor::new(&data[start..]);
while let Ok(entry) = SearchUpdate::read(&mut cursor) {
updates.push(entry);
}
updates
} else {
Default::default()
};
Ok(Self {
updates,
_phantom: PhantomData,
})
}
}
pub struct UnicodeSearchUpdateQueue {
inner: SearchUpdateQueueInner<UnicodePstFile>,
}
impl SearchUpdateQueue for UnicodeSearchUpdateQueue {
fn updates(&self) -> &[SearchUpdate] {
&self.inner.updates
}
}
impl SearchUpdateQueueReadWrite<UnicodePstFile> for UnicodeSearchUpdateQueue {
fn read(store: Rc<UnicodeStore>) -> io::Result<Rc<Self>> {
let inner = SearchUpdateQueueInner::read(store)?;
Ok(Rc::new(Self { inner }))
}
}
pub struct AnsiSearchUpdateQueue {
inner: SearchUpdateQueueInner<AnsiPstFile>,
}
impl SearchUpdateQueue for AnsiSearchUpdateQueue {
fn updates(&self) -> &[SearchUpdate] {
&self.inner.updates
}
}
impl SearchUpdateQueueReadWrite<AnsiPstFile> for AnsiSearchUpdateQueue {
fn read(store: Rc<AnsiStore>) -> io::Result<Rc<Self>> {
let inner = SearchUpdateQueueInner::read(store)?;
Ok(Rc::new(Self { inner }))
}
}