use core::cmp::Ordering;
use core::mem;
use alloc::vec;
use alloc::vec::Vec;
use binrw::io::{Read, Seek};
use nt_string::u16strle::U16StrLe;
use crate::attribute::NtfsAttributeType;
use crate::error::{NtfsError, Result};
use crate::file::KnownNtfsFileRecordNumber;
use crate::ntfs::Ntfs;
use crate::traits::NtfsReadSeek;
const UPCASE_CHARACTER_COUNT: usize = 65536;
const UPCASE_TABLE_SIZE: u64 = (UPCASE_CHARACTER_COUNT * mem::size_of::<u16>()) as u64;
#[derive(Clone, Debug)]
pub(crate) struct UpcaseTable {
uppercase_characters: Vec<u16>,
}
impl UpcaseTable {
pub(crate) fn read<T>(ntfs: &Ntfs, fs: &mut T) -> Result<Self>
where
T: Read + Seek,
{
let upcase_file = ntfs.file(fs, KnownNtfsFileRecordNumber::UpCase as u64)?;
let data_item = upcase_file
.data(fs, "")
.ok_or(NtfsError::AttributeNotFound {
position: upcase_file.position(),
ty: NtfsAttributeType::Data,
})??;
let data_attribute = data_item.to_attribute()?;
if data_attribute.value_length() != UPCASE_TABLE_SIZE {
return Err(NtfsError::InvalidUpcaseTableSize {
expected: UPCASE_TABLE_SIZE,
actual: data_attribute.value_length(),
});
}
let mut data_value = data_attribute.value(fs)?;
let mut data = vec![0u8; UPCASE_TABLE_SIZE as usize];
data_value.read_exact(fs, &mut data)?;
let uppercase_characters = data
.chunks_exact(2)
.map(|two_bytes| u16::from_le_bytes(two_bytes.try_into().unwrap()))
.collect();
Ok(Self {
uppercase_characters,
})
}
pub(crate) fn u16_to_uppercase(&self, character: u16) -> u16 {
self.uppercase_characters[character as usize]
}
}
pub trait UpcaseOrd<Rhs> {
fn upcase_cmp(&self, ntfs: &Ntfs, other: &Rhs) -> Ordering;
}
impl<'a, 'b> UpcaseOrd<U16StrLe<'a>> for U16StrLe<'b> {
fn upcase_cmp(&self, ntfs: &Ntfs, other: &U16StrLe<'a>) -> Ordering {
upcase_cmp_iter(self.u16_iter(), other.u16_iter(), ntfs)
}
}
impl<'a> UpcaseOrd<&str> for U16StrLe<'a> {
fn upcase_cmp(&self, ntfs: &Ntfs, other: &&str) -> Ordering {
upcase_cmp_iter(self.u16_iter(), other.encode_utf16(), ntfs)
}
}
impl<'a> UpcaseOrd<U16StrLe<'a>> for &str {
fn upcase_cmp(&self, ntfs: &Ntfs, other: &U16StrLe<'a>) -> Ordering {
upcase_cmp_iter(self.encode_utf16(), other.u16_iter(), ntfs)
}
}
fn upcase_cmp_iter<TI, OI>(mut this_iter: TI, mut other_iter: OI, ntfs: &Ntfs) -> Ordering
where
TI: Iterator<Item = u16>,
OI: Iterator<Item = u16>,
{
let upcase_table = ntfs.upcase_table();
loop {
match (this_iter.next(), other_iter.next()) {
(Some(this_code_unit), Some(other_code_unit)) => {
let this_upper = upcase_table.u16_to_uppercase(this_code_unit);
let other_upper = upcase_table.u16_to_uppercase(other_code_unit);
if this_upper != other_upper {
return this_upper.cmp(&other_upper);
}
}
(Some(_), None) => {
return Ordering::Greater;
}
(None, Some(_)) => {
return Ordering::Less;
}
(None, None) => {
return Ordering::Equal;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_upcase_table() {
let mut testfs1 = crate::helpers::tests::testfs1();
let ntfs = Ntfs::new(&mut testfs1).unwrap();
let upcase_table = UpcaseTable::read(&ntfs, &mut testfs1).unwrap();
for (lowercase, uppercase) in (b'a'..=b'z').zip(b'A'..=b'Z') {
assert_eq!(
upcase_table.u16_to_uppercase(lowercase as u16),
uppercase as u16
);
}
}
}