use alloc::vec;
use alloc::vec::Vec;
use crate::error::{FatError, Result};
use crate::io::{Read, Seek, SeekFrom};
use super::ExFatInfo;
pub struct UpcaseTable {
data: Vec<u16>,
valid: bool,
}
impl UpcaseTable {
const COMPRESSION_MARKER: u16 = 0xFFFF;
pub fn new() -> Self {
Self {
data: Vec::new(),
valid: false,
}
}
pub fn load<DATA: Read + Seek>(
&mut self,
data: &mut DATA,
info: &ExFatInfo,
first_cluster: u32,
size: u64,
is_contiguous: bool,
) -> Result<()> {
let mut raw_data = vec![0u8; size as usize];
if is_contiguous {
let offset = info.cluster_to_offset(first_cluster);
data.seek(SeekFrom::Start(offset))?;
data.read_exact(&mut raw_data)?;
} else {
return Err(FatError::UnsupportedFatType(
"fragmented exFAT upcase table not yet supported",
));
}
self.decompress(&raw_data)?;
self.valid = true;
Ok(())
}
fn decompress(&mut self, raw: &[u8]) -> Result<()> {
self.data.clear();
self.data.reserve(65536);
let mut i = 0;
let mut code_point: u32 = 0;
while i + 1 < raw.len() && code_point < 65536 {
let value = u16::from_le_bytes([raw[i], raw[i + 1]]);
i += 2;
if value == Self::COMPRESSION_MARKER && i + 1 < raw.len() {
let count = u16::from_le_bytes([raw[i], raw[i + 1]]) as u32;
i += 2;
for _ in 0..count {
if code_point >= 65536 {
break;
}
self.data.push(code_point as u16);
code_point += 1;
}
} else {
self.data.push(value);
code_point += 1;
}
}
while code_point < 65536 {
self.data.push(code_point as u16);
code_point += 1;
}
self.data.truncate(65536);
Ok(())
}
pub fn to_upper(&self, c: u16) -> u16 {
if self.valid && (c as usize) < self.data.len() {
self.data[c as usize]
} else {
c
}
}
pub fn names_equal(&self, name1: &str, name2: &str) -> bool {
let chars1: Vec<u16> = name1.encode_utf16().collect();
let chars2: Vec<u16> = name2.encode_utf16().collect();
if chars1.len() != chars2.len() {
return false;
}
for (&c1, &c2) in chars1.iter().zip(chars2.iter()) {
if self.to_upper(c1) != self.to_upper(c2) {
return false;
}
}
true
}
pub fn name_hash(&self, name: &str) -> u16 {
let mut hash: u16 = 0;
for code_unit in name.encode_utf16() {
let upper = self.to_upper(code_unit);
hash = hash.rotate_right(1).wrapping_add((upper & 0xFF) as u16);
hash = hash.rotate_right(1).wrapping_add((upper >> 8) as u16);
}
hash
}
pub fn is_valid(&self) -> bool {
self.valid
}
pub fn create_default() -> Self {
let mut data = Vec::with_capacity(65536);
for i in 0u16..=65535 {
let upper = if i >= 0x61 && i <= 0x7A {
i - 0x20
} else {
i
};
data.push(upper);
}
Self { data, valid: true }
}
}
impl Default for UpcaseTable {
fn default() -> Self {
Self::new()
}
}
pub fn compute_upcase_checksum(data: &[u8]) -> u32 {
let mut checksum: u32 = 0;
for &byte in data {
checksum = checksum.rotate_right(1).wrapping_add(byte as u32);
}
checksum
}
#[cfg(feature = "write")]
pub fn generate_compressed_upcase_table() -> (Vec<u8>, u32) {
let mut data = Vec::with_capacity(5000);
data.extend_from_slice(&UpcaseTable::COMPRESSION_MARKER.to_le_bytes());
data.extend_from_slice(&97u16.to_le_bytes());
for i in 0x0041u16..=0x005A {
data.extend_from_slice(&i.to_le_bytes());
}
data.extend_from_slice(&UpcaseTable::COMPRESSION_MARKER.to_le_bytes());
data.extend_from_slice(&101u16.to_le_bytes());
for i in 0x00C0u16..=0x00D6 {
data.extend_from_slice(&i.to_le_bytes());
}
data.extend_from_slice(&0x00F7u16.to_le_bytes());
for i in 0x00D8u16..=0x00DE {
data.extend_from_slice(&i.to_le_bytes());
}
data.extend_from_slice(&0x0178u16.to_le_bytes());
data.extend_from_slice(&UpcaseTable::COMPRESSION_MARKER.to_le_bytes());
data.extend_from_slice(&32768u16.to_le_bytes());
data.extend_from_slice(&UpcaseTable::COMPRESSION_MARKER.to_le_bytes());
data.extend_from_slice(&32512u16.to_le_bytes());
let checksum = compute_upcase_checksum(&data);
(data, checksum)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_upcase_ascii() {
let table = UpcaseTable::create_default();
assert_eq!(table.to_upper(b'a' as u16), b'A' as u16);
assert_eq!(table.to_upper(b'z' as u16), b'Z' as u16);
assert_eq!(table.to_upper(b'A' as u16), b'A' as u16);
assert_eq!(table.to_upper(b'0' as u16), b'0' as u16);
}
#[test]
fn test_names_equal() {
let table = UpcaseTable::create_default();
assert!(table.names_equal("hello", "HELLO"));
assert!(table.names_equal("Test.txt", "test.TXT"));
assert!(!table.names_equal("hello", "world"));
assert!(!table.names_equal("hello", "hello!"));
}
}