608 lines
20 KiB
Rust
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(()) }
|
|
}
|
|
}
|