use std::{ fs::{self, File}, io::{Read, Seek, SeekFrom, Write}, path::Path }; use game::{ history::Play, Game, }; use crate::{ app::{ session::{Session, SessionToken, SessionSecret}, user::{User, UserStatus}, }, util::pack::*, }; const HANDLE_BUCKET_MASK :u32 = 0xFF; const HANDLE_BUCKET_SIZE :u32 = HANDLE_BUCKET_MASK + 1; const GENERIC_CONFIG :&str = "c.bin"; const GENERIC_HISTORY :&str = "h.bin"; const GENERIC_INDEX :&str = "i.bin"; const GENERIC_REQUEST :&str = "r.bin"; const GENERIC_STATUS :&str = "s.bin"; const DIR_DATA :&str = "data"; const DIR_HANDLE :&str = const_format::formatcp!("{}/h", DIR_DATA); const DIR_SESSION :&str = const_format::formatcp!("{}/s", DIR_DATA); const DIR_USER :&str = const_format::formatcp!("{}/u", DIR_DATA); const INDEX_HANDLE :&str = const_format::formatcp!("{d}/{f}", d= DIR_HANDLE, f= GENERIC_INDEX); const INDEX_SESSION :&str = const_format::formatcp!("{d}/{f}", d= DIR_SESSION, f= GENERIC_INDEX); const INDEX_USER :&str = const_format::formatcp!("{d}/{f}", d= DIR_USER, f= GENERIC_INDEX); pub const FILE_SALT :&str = const_format::formatcp!("{}/x.bin", DIR_DATA); pub struct FileSystem { index_handle:File, index_session:File, index_user:File, table_salt:File, } impl FileSystem { pub fn init() -> Result { // // TEMPORARY: REMOVE AFTER TESTING // //fs::remove_dir_all(DIR_DATA).ok(); // Initialize filesystem if does not exist. // // Notice: does not currently check for corruption. // if !Path::new(DIR_DATA).exists() { fs::create_dir(DIR_DATA)?; fs::create_dir(DIR_HANDLE)?; fs::create_dir(DIR_SESSION)?; fs::create_dir(DIR_USER)?; fs::write(INDEX_HANDLE, pack_u32(0))?; fs::write(INDEX_SESSION, pack_u32(0))?; fs::write(INDEX_USER, pack_u32(0))?; fs::write(FILE_SALT, pack_u32(0))?; } let index_handle = File::options() .read(true) .write(true) .open(INDEX_HANDLE)?; let index_session = File::options() .read(true) .write(true) .open(INDEX_SESSION)?; let index_user = File::options() .read(true) .write(true) .open(INDEX_USER)?; let table_salt = File::options() .read(true) .write(true) .open(FILE_SALT)?; Ok(Self { index_handle, index_session, index_user, table_salt, }) } pub fn session_create(&mut self, session:&Session) -> Result { let size = self.session_count()? as u32; // Update size record self.index_session.seek(SeekFrom::Start(0)).map_err(|_| ())?; self.index_session.write(&pack_u32(size + 1)).map_err(|_| ())?; // Write session config file match self.session_update(size, session) { Ok(_) => { // Create session history file let bucket_index = size & !HANDLE_BUCKET_MASK; let dir_index = size & HANDLE_BUCKET_MASK; let bucket_path = Path::new(DIR_SESSION) .join(format!("{:08x}", bucket_index)) .join(format!("{:08x}", dir_index)); fs::write(bucket_path.join(GENERIC_HISTORY), &[0; 2]).map_err(|_| ())?; Ok(size) } Err(_) => Err(()), } } pub fn session_update(&mut self, id:u32, session:&Session) -> Result<(),()> { let bucket_index = id & !HANDLE_BUCKET_MASK; let dir_index = id & HANDLE_BUCKET_MASK; // Create bucket file if not exists let bucket_path = Path::new(DIR_SESSION) .join(format!("{:08x}", bucket_index)) .join(format!("{:08x}", dir_index)); if !bucket_path.exists() { fs::create_dir_all(bucket_path.clone()).map_err(|_| ())?; } // Open session config file if let Ok(mut file) = File::options().write(true).create(true).open(bucket_path.join(GENERIC_CONFIG)) { // Write session information file.write(&session.token).map_err(|_| ())?; file.write(&session.secret).map_err(|_| ())?; file.write(&pack_u8(0)).map_err(|_| ())?; file.write(&pack_u32(session.p_dawn.user)).map_err(|_| ())?; file.write(&pack_u8(0)).map_err(|_| ())?; file.write(&pack_u32(session.p_dusk.user)).map_err(|_| ())?; file.write(&pack_u64(session.time)).map_err(|_| ())?; Ok(()) } else { Err(()) } } pub fn session_fetch(&mut self, id:u32) -> Result { use crate::app::session::Player; let bucket_index = id & !HANDLE_BUCKET_MASK; let dir_index = id & HANDLE_BUCKET_MASK; // Create bucket file if not exists let bucket_path = Path::new(DIR_SESSION) .join(format!("{:08x}", bucket_index)) .join(format!("{:08x}", dir_index)); // Open session config file if let Ok(mut file) = File::options().read(true).open(bucket_path.join(GENERIC_CONFIG)) { // Extract session information let mut buffer_u8 = [0u8; 1]; let mut buffer_u32 = [0u8; 4]; let mut buffer_u64 = [0u8; 8]; let mut token = SessionToken::default(); let mut secret = SessionSecret::default(); file.read_exact(&mut token).map_err(|_| ())?; file.read_exact(&mut secret).map_err(|_| ())?; file.read_exact(&mut buffer_u8).map_err(|_| ())?; file.read_exact(&mut buffer_u32).map_err(|_| ())?; let dawn = unpack_u32(&buffer_u32, &mut 0); file.read_exact(&mut buffer_u8).map_err(|_| ())?; file.read_exact(&mut buffer_u32).map_err(|_| ())?; let dusk = unpack_u32(&buffer_u32, &mut 0); file.read_exact(&mut buffer_u64).map_err(|_| ())?; let time = unpack_u64(&buffer_u64, &mut 0); Ok(Session { id, token, secret, game:Game::new(), p_dawn:Player { user:dawn, connections:Vec::new(), }, p_dusk:Player { user:dusk, connections:Vec::new(), }, connections:Vec::new(), time, chain_id:0, undo:None, }) } else { Err(()) } } pub fn session_history_push(&mut self, id:u32, history:Play) -> Result<(),()> { let play_data :u16 = (history.source as u16) | ((history.from as u16) << 4) | ((history.to as u16) << 10); let bucket_index = id & !HANDLE_BUCKET_MASK; let dir_index = id & HANDLE_BUCKET_MASK; let bucket_path = Path::new(DIR_SESSION) .join(format!("{:08x}", bucket_index)) .join(format!("{:08x}", dir_index)); // Open session history file if let Ok(mut file) = File::options().read(true).write(true).open(bucket_path.join(GENERIC_HISTORY)) { let mut buffer_size = [0u8; 2]; // Append history information file.seek(SeekFrom::End(0)).map_err(|_| ())?; file.write(&pack_u16(play_data)).map_err(|_| ())?; // Update length file.seek(SeekFrom::Start(0)).map_err(|_| ())?; file.read_exact(&mut buffer_size).map_err(|_| ())?; file.seek(SeekFrom::Start(0)).map_err(|_| ())?; let size = unpack_u16(&buffer_size, &mut 0); file.write(&pack_u16(size + 1)).map_err(|_| ())?; Ok(()) } else { Err(()) } } pub fn session_history_fetch(&mut self, id:u32) -> Result,()> { let mut result = Vec::new(); let bucket_index = id & !HANDLE_BUCKET_MASK; let dir_index = id & HANDLE_BUCKET_MASK; let bucket_path = Path::new(DIR_SESSION) .join(format!("{:08x}", bucket_index)) .join(format!("{:08x}", dir_index)); // Open session history file if let Ok(mut file) = File::options().read(true).open(bucket_path.join(GENERIC_HISTORY)) { // Read history length let mut buffer = [0u8; 2]; file.seek(SeekFrom::Start(0)).map_err(|_| ())?; file.read_exact(&mut buffer).map_err(|_| ())?; let length = unpack_u16(&buffer, &mut 0); // Read for _ in 0..length { file.read_exact(&mut buffer).map_err(|_| ())?; let data = unpack_u16(&buffer, &mut 0); result.push(Play { source:(data & 0xF) as u8, from:((data >> 4) & 0x3F) as u8, to:((data >> 10) & 0x3F) as u8, }); } Ok(result) } else { Err(()) } } pub fn session_count(&mut self) -> Result // Get number of salts in store. // { Self::get_header_size(&mut self.index_session) } pub fn user_create(&mut self, user:&User) -> Result { let size = self.user_count()? as u32; let bucket_index = size & !HANDLE_BUCKET_MASK; let file_index = size & HANDLE_BUCKET_MASK; // Update size record self.index_user.seek(SeekFrom::Start(0)).map_err(|_| ())?; self.index_user.write(&pack_u32(size + 1)).map_err(|_| ())?; // Create user directory let bucket_path = Path::new(DIR_USER) .join(format!("{:08x}", bucket_index)) .join(format!("{:08x}", file_index)); fs::create_dir_all(bucket_path.clone()).map_err(|_| ())?; self.user_update(size, user)?; // Create status file let file_path = bucket_path.join(GENERIC_STATUS); if let Ok(mut file) = File::options().write(true).create(true).open(file_path) { let extra_buffer = [0u8; 12]; let contest = 0; // Write user information file.write(&pack_u32(contest)).map_err(|_| ())?; file.write(&extra_buffer).map_err(|_| ())?; } // Create challenges file let file_path = bucket_path.join(GENERIC_REQUEST); if let Ok(mut file) = File::options().write(true).create(true).open(file_path) { // Write requests count file.write(&pack_u16(0)).map_err(|_| ())?; } Ok(size) } pub fn user_update(&mut self, id:u32, user:&User) -> Result<(),()> { let bucket_index = id & !HANDLE_BUCKET_MASK; let file_index = id & HANDLE_BUCKET_MASK; let bucket_path = Path::new(DIR_USER) .join(format!("{:08x}", bucket_index)) .join(format!("{:08x}", file_index)); // Create configuration file let file_path = bucket_path.join(GENERIC_CONFIG); if let Ok(mut file) = File::options().write(true).create(true).open(file_path) { let handle = user.handle.as_bytes().to_vec(); // Write user information file.write(&pack_u32(user.flags)).map_err(|_| ())?; file.write(&pack_u32(user.na_key)).map_err(|_| ())?; file.write(&pack_u16(user.secret.len() as u16)).map_err(|_| ())?; file.write(&user.secret).map_err(|_| ())?; file.write(&pack_u8(handle.len() as u8)).map_err(|_| ())?; file.write(&handle).map_err(|_| ())?; } Ok(()) } pub fn user_update_status(&mut self) -> Result<(),()> { Err(()) } pub fn user_fetch(&mut self, id:u32) -> Result // Retrieve a salt from store. // { let bucket_index = id & !HANDLE_BUCKET_MASK; let file_index = id & HANDLE_BUCKET_MASK; // Open bucket file for record let file_path = Path::new(DIR_USER) .join(format!("{:08x}", bucket_index)) .join(format!("{:08x}", file_index)) .join(GENERIC_CONFIG); if let Ok(mut file) = File::options().read(true).open(file_path) { file.seek(SeekFrom::Start(0)).map_err(|_| ())?; // Extract user information let mut buffer_u8 = [0u8; 1]; let mut buffer_u16 = [0u8; 2]; let mut buffer_u32 = [0u8; 4]; file.read_exact(&mut buffer_u32).map_err(|_| ())?; let flags = unpack_u32(&buffer_u32, &mut 0); file.read_exact(&mut buffer_u32).map_err(|_| ())?; let na_key = unpack_u32(&buffer_u32, &mut 0); file.read_exact(&mut buffer_u16).map_err(|_| ())?; let secret_length = unpack_u16(&buffer_u16, &mut 0); let mut secret = vec![0u8; (secret_length & 0x7FF) as usize]; file.read_exact(&mut secret).map_err(|_| ())?; if (secret_length & 0x8000) != 0 { secret.clear(); } file.read_exact(&mut buffer_u8).map_err(|_| ())?; let handle_length = unpack_u8(&buffer_u8, &mut 0); let mut handle = vec![0u8; handle_length as usize]; file.read_exact(&mut handle).map_err(|_| ())?; let handle = String::from_utf8(handle).map_err(|_| ())?; Ok(User { id, flags, handle, secret, na_key, connection:None, status:UserStatus::new(), challenges:Vec::new(), }) } else { Err(()) } } pub fn user_count(&mut self) -> Result // Get number of salts in store. // { Self::get_header_size(&mut self.index_user) } pub fn handle_create(&mut self, handle:&String, user_id:u32) -> Result // Add a salt to store. // { let size = self.handle_count()? as u32; let data = handle.as_bytes(); let length = data.len(); let bucket_index = size & !HANDLE_BUCKET_MASK; let record_index = size & HANDLE_BUCKET_MASK; let record_offset = 12 * record_index as u64; // Update size record self.index_handle.seek(SeekFrom::Start(0)).map_err(|_| ())?; self.index_handle.write(&pack_u32(size + 1)).map_err(|_| ())?; // Create bucket file if not exists let bucket_path = Path::new(DIR_HANDLE).join(format!("{:08x}.bin", bucket_index)); if !bucket_path.exists() { fs::write(bucket_path.clone(), vec![0u8; 12 * HANDLE_BUCKET_SIZE as usize]).map_err(|_| ())?; } // Open bucket file for record if let Ok(mut file) = File::options().read(true).write(true).open(bucket_path) { let offset = file.seek(SeekFrom::End(0)).map_err(|_| ())?; // Write record header to table let buffer_header = [ pack_u32(offset as u32), pack_u32(length as u32), pack_u32(user_id), ].concat(); file.seek(SeekFrom::Start(record_offset)).map_err(|_| ())?; file.write(&buffer_header).map_err(|_| ())?; // Write handle data to end of file file.seek(SeekFrom::End(0)).map_err(|_| ())?; file.write(data).map_err(|_| ())?; Ok(size) } else { Err(()) } } pub fn handle_fetch(&mut self, id:u32) -> Result<(String, u32),()> // Retrieve a salt from store. // { let size = self.salt_count()? as u32; let mut buffer = Vec::new(); // Get location of handle by index if id < size { let bucket_index = id & !HANDLE_BUCKET_MASK; let record_index = id & HANDLE_BUCKET_MASK; let record_offset = 12 * record_index as u64; let path = Path::new(DIR_HANDLE) .join(format!("{:08x}.bin", bucket_index)); // Read bucket file for record if let Ok(mut file) = File::open(path) { // Get record offset, length, and user association let mut buffer_header = [0u8; 12]; file.seek(SeekFrom::Start(record_offset)).map_err(|_| ())?; if file.read_exact(&mut buffer_header).is_ok() { let offset = unpack_u32(&buffer_header, &mut 0); let length = unpack_u32(&buffer_header, &mut 4); let user_id = unpack_u32(&buffer_header, &mut 8); if offset != 0 { buffer.resize(length as usize, 0); // Read handle data from offset file.seek(SeekFrom::Start(offset as u64)).map_err(|_| ())?; match file.read_exact(&mut buffer) { Ok(_) => { Ok((String::from_utf8(buffer).map_err(|_| ())?, user_id)) } Err(e) => { println!("e: {}", e.to_string()); Err(()) } } } else { Err(()) } } else { Err(()) } } else { Err(()) } } else { Err(()) } } pub fn handle_count(&mut self) -> Result // Get number of salts in store. // { Self::get_header_size(&mut self.index_handle) } pub fn salt_create(&mut self, salt:[u8; 16]) -> Result // Add a salt to store. // { let size = self.salt_count()? as u32; // Update size record self.table_salt.seek(SeekFrom::Start(0)).map_err(|_| ())?; self.table_salt.write(&pack_u32(size + 1)).map_err(|_| ())?; // Write salt to store self.table_salt.seek(SeekFrom::End(0)).map_err(|_| ())?; self.table_salt.write(&salt).map_err(|_| ())?; Ok(size) } pub fn salt_fetch(&mut self, id:u32) -> Result<[u8; 16],()> // Retrieve a salt from store. // { let size = self.salt_count()? as u32; let mut buffer = [0u8; 16]; if id < size { let offset = 4 + (16 * id as u64); self.table_salt.seek(SeekFrom::Start(offset)).map_err(|_| ())?; if self.table_salt.read_exact(&mut buffer).is_ok() { Ok(buffer) } else { Err(()) } } else { Err(()) } } pub fn salt_count(&mut self) -> Result // Get number of salts in store. // { Self::get_header_size(&mut self.table_salt) } fn get_header_size(file:&mut File) -> Result { let mut size_buffer = [0u8; 4]; file.seek(SeekFrom::Start(0)).map_err(|_| ())?; if file.read_exact(&mut size_buffer).is_ok() { Ok(unpack_u32(&size_buffer, &mut 0) as usize) } else { Err(()) } } }