use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::{
fmt::Debug,
io::{self, Read, Write},
};
use super::{read_write::NodeIdReadWrite, *};
#[repr(u8)]
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum NodeIdType {
HeapNode = 0x00,
Internal = 0x01,
NormalFolder = 0x02,
SearchFolder = 0x03,
NormalMessage = 0x04,
Attachment = 0x05,
SearchUpdateQueue = 0x06,
SearchCriteria = 0x07,
AssociatedMessage = 0x08,
ContentsTableIndex = 0x0A,
ReceiveFolderTable = 0x0B,
OutgoingQueueTable = 0x0C,
HierarchyTable = 0x0D,
ContentsTable = 0x0E,
AssociatedContentsTable = 0x0F,
SearchContentsTable = 0x10,
AttachmentTable = 0x11,
RecipientTable = 0x12,
SearchTableIndex = 0x13,
ListsTablesProperties = 0x1F,
}
impl TryFrom<u8> for NodeIdType {
type Error = NdbError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(NodeIdType::HeapNode),
0x01 => Ok(NodeIdType::Internal),
0x02 => Ok(NodeIdType::NormalFolder),
0x03 => Ok(NodeIdType::SearchFolder),
0x04 => Ok(NodeIdType::NormalMessage),
0x05 => Ok(NodeIdType::Attachment),
0x06 => Ok(NodeIdType::SearchUpdateQueue),
0x07 => Ok(NodeIdType::SearchCriteria),
0x08 => Ok(NodeIdType::AssociatedMessage),
0x0A => Ok(NodeIdType::ContentsTableIndex),
0x0B => Ok(NodeIdType::ReceiveFolderTable),
0x0C => Ok(NodeIdType::OutgoingQueueTable),
0x0D => Ok(NodeIdType::HierarchyTable),
0x0E => Ok(NodeIdType::ContentsTable),
0x0F => Ok(NodeIdType::AssociatedContentsTable),
0x10 => Ok(NodeIdType::SearchContentsTable),
0x11 => Ok(NodeIdType::AttachmentTable),
0x12 => Ok(NodeIdType::RecipientTable),
0x13 => Ok(NodeIdType::SearchTableIndex),
0x1F => Ok(NodeIdType::ListsTablesProperties),
_ => Err(NdbError::InvalidNodeIdType(value)),
}
}
}
pub const MAX_NODE_INDEX: u32 = 1_u32.rotate_right(5) - 1;
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct NodeId(u32);
impl NodeId {
pub fn new(id_type: NodeIdType, index: u32) -> NdbResult<Self> {
let id_type = id_type as u8;
if id_type >> 5 != 0 {
return Err(NdbError::InvalidNodeIdType(id_type));
}
let shifted_index = index.rotate_left(5);
if shifted_index & 0x1F != 0 {
return Err(NdbError::InvalidNodeIndex(index));
};
Ok(Self(shifted_index | (u32::from(id_type))))
}
pub fn id_type(&self) -> NdbResult<NodeIdType> {
let nid_type = self.0 & 0x1F;
NodeIdType::try_from(nid_type as u8)
}
pub fn index(&self) -> u32 {
self.0 >> 5
}
}
impl NodeIdReadWrite for NodeId {
fn new(id_type: NodeIdType, index: u32) -> NdbResult<Self> {
Self::new(id_type, index)
}
fn read(f: &mut dyn Read) -> io::Result<Self> {
let value = f.read_u32::<LittleEndian>()?;
Ok(Self(value))
}
fn write(&self, f: &mut dyn Write) -> io::Result<()> {
f.write_u32::<LittleEndian>(self.0)
}
}
impl Debug for NodeId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Ok(id_type) = self.id_type() else {
return write!(f, "NodeId {{ invalid: 0x{:08X} }}", u32::from(*self));
};
write!(f, "NodeId {{ {:?}: 0x{:X} }}", id_type, self.index())
}
}
impl From<u32> for NodeId {
fn from(value: u32) -> Self {
Self(value)
}
}
impl From<NodeId> for u32 {
fn from(value: NodeId) -> Self {
value.0
}
}
pub const NID_MESSAGE_STORE: NodeId = NodeId(0x21);
pub const NID_NAME_TO_ID_MAP: NodeId = NodeId(0x61);
pub const NID_NORMAL_FOLDER_TEMPLATE: NodeId = NodeId(0xA1);
pub const NID_SEARCH_FOLDER_TEMPLATE: NodeId = NodeId(0xC1);
pub const NID_ROOT_FOLDER: NodeId = NodeId(0x122);
pub const NID_SEARCH_MANAGEMENT_QUEUE: NodeId = NodeId(0x1E1);
pub const NID_SEARCH_ACTIVITY_LIST: NodeId = NodeId(0x201);
pub const NID_RESERVED1: NodeId = NodeId(0x241);
pub const NID_SEARCH_DOMAIN_OBJECT: NodeId = NodeId(0x261);
pub const NID_SEARCH_GATHERER_QUEUE: NodeId = NodeId(0x281);
pub const NID_SEARCH_GATHERER_DESCRIPTOR: NodeId = NodeId(0x2A1);
pub const NID_RESERVED2: NodeId = NodeId(0x2E1);
pub const NID_RESERVED3: NodeId = NodeId(0x301);
pub const NID_SEARCH_GATHERER_FOLDER_QUEUE: NodeId = NodeId(0x321);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nid_index_overflow() {
let Err(NdbError::InvalidNodeIndex(value)) =
NodeId::new(NodeIdType::HeapNode, MAX_NODE_INDEX + 1)
else {
panic!("NodeId should be out of range");
};
assert_eq!(value, MAX_NODE_INDEX + 1);
}
}