608 lines
20 KiB
Rust

use std::{
fs::{self, File}, io::{Read, Seek, SeekFrom, Write}, path::Path
};
use const_format::*;
use game::{
history::Play,
Game,
};
use crate::{
app::{
session::{Session, SessionToken, SessionSecret},
user::User,
},
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 = formatcp!("{}/h", DIR_DATA);
const DIR_SESSION :&str = formatcp!("{}/s", DIR_DATA);
const DIR_USER :&str = formatcp!("{}/u", DIR_DATA);
const INDEX_HANDLE :&str = formatcp!("{d}/{f}", d= DIR_HANDLE, f= GENERIC_INDEX);
const INDEX_SESSION :&str = formatcp!("{d}/{f}", d= DIR_SESSION, f= GENERIC_INDEX);
const INDEX_USER :&str = formatcp!("{d}/{f}", d= DIR_USER, f= GENERIC_INDEX);
pub const FILE_SALT :&str = 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<Self, std::io::Error>
{
//
// 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<u32,()>
{
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<Session,()>
{
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:0,
})
} 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_pop(&mut self, id:u32) -> Result<(),()>
{
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];
// Update length
file.seek(SeekFrom::Start(0)).map_err(|_| ())?;
file.read_exact(&mut buffer_size).map_err(|_| ())?;
let size = unpack_u16(&buffer_size, &mut 0);
if size > 0 {
let new_size = size - 1;
file.seek(SeekFrom::Start(0)).map_err(|_| ())?;
file.write(&pack_u16(new_size)).map_err(|_| ())?;
file.set_len(2 + (2 * new_size as u64)).map_err(|_| ())?;
}
Ok(())
} else { Err(()) }
}
pub fn session_history_fetch(&mut self, id:u32) -> Result<Vec<Play>,()>
{
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);
let play = Play {
source:(data & 0xF) as u8,
from:((data >> 4) & 0x3F) as u8,
to:((data >> 10) & 0x3F) as u8,
meta:0,
};
result.push(play);
}
Ok(result)
} else { Err(()) }
}
pub fn session_count(&mut self) -> Result<usize, ()>
// Get number of salts in store.
//
{
Self::get_header_size(&mut self.index_session)
}
pub fn user_create(&mut self, user:&User) -> Result<u32,()>
{
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<User,()>
// 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(|_| ())?;
let mut user = User::new();
user.id = id;
user.flags = flags;
user.handle = handle;
user.secret = secret;
user.na_key = na_key;
Ok(user)
} else { Err(()) }
}
pub fn user_count(&mut self) -> Result<usize, ()>
// 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<u32,()>
// 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<usize, ()>
// Get number of salts in store.
//
{
Self::get_header_size(&mut self.index_handle)
}
pub fn salt_create(&mut self, salt:[u8; 16]) -> Result<u32,()>
// 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<usize, ()>
// Get number of salts in store.
//
{
Self::get_header_size(&mut self.table_salt)
}
fn get_header_size(file:&mut File) -> Result<usize, ()>
{
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(()) }
}
}