use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::io::{self, Cursor, Read, Seek, Write};
use super::{read_write::*, *};
use crate::ndb::{
block::{AnsiDataTree, UnicodeDataTree},
header::NdbCryptMethod,
node_id::NodeId,
page::{
AnsiBlockBTree, AnsiNodeBTreeEntry, NodeBTreeEntry, RootBTree, UnicodeBlockBTree,
UnicodeNodeBTreeEntry,
},
};
#[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 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)
}
}
pub struct UnicodeSearchUpdateQueue {
updates: Vec<SearchUpdate>,
}
impl UnicodeSearchUpdateQueue {
pub fn read<R: Read + Seek>(
f: &mut R,
encoding: NdbCryptMethod,
block_btree: &UnicodeBlockBTree,
node: UnicodeNodeBTreeEntry,
) -> io::Result<Self> {
let start = node.parent().map(u32::from).unwrap_or_default();
if start % 20 != 0 {
return Err(MessagingError::InvalidSearchUpdateQueueOffset(start).into());
}
let start = u16::try_from(start)
.map_err(|_| MessagingError::InvalidSearchUpdateQueueOffset(start))?;
let key = u64::from(node.data());
if key == 0 {
return Ok(Self {
updates: Default::default(),
});
}
let block = block_btree.find_entry(f, key)?;
let tree = UnicodeDataTree::read(f, encoding, &block)?;
let data: Vec<_> = tree
.blocks(f, encoding, block_btree)?
.flat_map(Vec::from)
.skip(start as usize)
.collect();
let size = data.len();
if size % 20 != 0 {
return Err(MessagingError::InvalidSearchUpdateQueueSize(size).into());
}
let count = size / 20;
let mut updates = Vec::with_capacity(count);
let mut cursor = Cursor::new(data);
while let Ok(entry) = SearchUpdate::read(&mut cursor) {
updates.push(entry);
}
Ok(Self { updates })
}
pub fn updates(&self) -> &[SearchUpdate] {
&self.updates
}
}
pub struct AnsiSearchUpdateQueue {
updates: Vec<SearchUpdate>,
}
impl AnsiSearchUpdateQueue {
pub fn read<R: Read + Seek>(
f: &mut R,
encoding: NdbCryptMethod,
block_btree: &AnsiBlockBTree,
node: AnsiNodeBTreeEntry,
) -> io::Result<Self> {
let start = node.parent().map(u32::from).unwrap_or_default();
if start % 20 != 0 {
return Err(MessagingError::InvalidSearchUpdateQueueOffset(start).into());
}
let start = u16::try_from(start)
.map_err(|_| MessagingError::InvalidSearchUpdateQueueOffset(start))?;
let key = u32::from(node.data());
if key == 0 {
return Ok(Self {
updates: Default::default(),
});
}
let block = block_btree.find_entry(f, key)?;
let tree = AnsiDataTree::read(f, encoding, &block)?;
let data: Vec<_> = tree
.blocks(f, encoding, block_btree)?
.flat_map(Vec::from)
.skip(start as usize)
.collect();
let size = data.len();
if size % 20 != 0 {
return Err(MessagingError::InvalidSearchUpdateQueueSize(size).into());
}
let count = size / 20;
let mut updates = Vec::with_capacity(count);
let mut cursor = Cursor::new(data);
while let Ok(entry) = SearchUpdate::read(&mut cursor) {
updates.push(entry);
}
Ok(Self { updates })
}
pub fn updates(&self) -> &[SearchUpdate] {
&self.updates
}
}