From 7a4600b3a232afa647ee964ebce900a9f09608bc Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 18 Oct 2026 03:37:40 +0200 Subject: [PATCH] feat(storage-server): add StorageServer with BlockDevice trait, mock adapters, and tests --- crates/servers/storage-server/Cargo.toml | 2 + .../storage-server/src/adapters/mock_block.rs | 46 +++++++ .../storage-server/src/adapters/mock_fs.rs | 127 ++++++++++++++++++ .../storage-server/src/adapters/mod.rs | 2 + .../storage-server/src/block_device.rs | 6 + .../servers/storage-server/src/filesystem.rs | 46 +++++++ crates/servers/storage-server/src/fs/mod.rs | 3 + crates/servers/storage-server/src/lib.rs | 22 ++- crates/servers/storage-server/src/protocol.rs | 88 ++++++++++++ crates/servers/storage-server/src/server.rs | 100 ++++++++++++++ .../servers/storage-server/src/tests/mod.rs | 2 + .../storage-server/src/tests/protocol.rs | 64 +++++++++ .../storage-server/src/tests/server.rs | 95 +++++++++++++ 13 files changed, 591 insertions(+), 12 deletions(-) create mode 100644 crates/servers/storage-server/src/adapters/mock_block.rs create mode 100644 crates/servers/storage-server/src/adapters/mock_fs.rs create mode 100644 crates/servers/storage-server/src/adapters/mod.rs create mode 100644 crates/servers/storage-server/src/block_device.rs create mode 100644 crates/servers/storage-server/src/filesystem.rs create mode 100644 crates/servers/storage-server/src/fs/mod.rs create mode 100644 crates/servers/storage-server/src/protocol.rs create mode 100644 crates/servers/storage-server/src/server.rs create mode 100644 crates/servers/storage-server/src/tests/mod.rs create mode 100644 crates/servers/storage-server/src/tests/protocol.rs create mode 100644 crates/servers/storage-server/src/tests/server.rs diff --git a/crates/servers/storage-server/Cargo.toml b/crates/servers/storage-server/Cargo.toml index 32c0100..2593d6f 100644 --- a/crates/servers/storage-server/Cargo.toml +++ b/crates/servers/storage-server/Cargo.toml @@ -4,3 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] +types = { workspace = true } +server-framework = { workspace = true } diff --git a/crates/servers/storage-server/src/adapters/mock_block.rs b/crates/servers/storage-server/src/adapters/mock_block.rs new file mode 100644 index 0000000..15fb976 --- /dev/null +++ b/crates/servers/storage-server/src/adapters/mock_block.rs @@ -0,0 +1,46 @@ +use crate::block_device::BlockDevice; + +const SECTOR_SIZE: usize = 512; +const MAX_SECTORS: usize = 64; + +pub struct MockBlockDevice { + data: [[u8; SECTOR_SIZE]; MAX_SECTORS], + sectors: usize, +} + +impl MockBlockDevice { + pub fn new(sectors: usize) -> Self { + Self { + data: [[0; SECTOR_SIZE]; MAX_SECTORS], + sectors: sectors.min(MAX_SECTORS), + } + } +} + +impl BlockDevice for MockBlockDevice { + fn sector_size(&self) -> usize { + SECTOR_SIZE + } + + fn sector_count(&self) -> usize { + self.sectors + } + + fn read_sector(&mut self, sector: u32, buf: &mut [u8]) -> bool { + let idx = sector as usize; + if idx >= self.sectors || buf.len() < SECTOR_SIZE { + return false; + } + buf[..SECTOR_SIZE].copy_from_slice(&self.data[idx]); + true + } + + fn write_sector(&mut self, sector: u32, buf: &[u8]) -> bool { + let idx = sector as usize; + if idx >= self.sectors || buf.len() < SECTOR_SIZE { + return false; + } + self.data[idx].copy_from_slice(&buf[..SECTOR_SIZE]); + true + } +} diff --git a/crates/servers/storage-server/src/adapters/mock_fs.rs b/crates/servers/storage-server/src/adapters/mock_fs.rs new file mode 100644 index 0000000..cec7134 --- /dev/null +++ b/crates/servers/storage-server/src/adapters/mock_fs.rs @@ -0,0 +1,127 @@ +use crate::filesystem::{DirEntry, FileHandle, FileSystem}; + +const MAX_FILES: usize = 16; +const MAX_FILE_SIZE: usize = 256; + +struct FileEntry { + name: [u8; 32], + name_len: u8, + data: [u8; MAX_FILE_SIZE], + size: usize, + open: bool, +} + +impl FileEntry { + fn empty() -> Self { + Self { + name: [0; 32], + name_len: 0, + data: [0; MAX_FILE_SIZE], + size: 0, + open: false, + } + } +} + +pub struct MockFileSystem { + files: [Option; MAX_FILES], + count: usize, +} + +impl MockFileSystem { + pub fn new() -> Self { + Self { + files: [const { None }; MAX_FILES], + count: 0, + } + } + + fn find_by_name(&self, path: &[u8]) -> Option { + for i in 0..self.count { + if let Some(entry) = &self.files[i] { + if &entry.name[..entry.name_len as usize] == path { + return Some(i); + } + } + } + None + } +} + +impl FileSystem for MockFileSystem { + fn open(&mut self, path: &[u8]) -> Option { + let idx = self.find_by_name(path)?; + if let Some(entry) = &mut self.files[idx] { + entry.open = true; + Some(FileHandle::new(idx as u16)) + } else { + None + } + } + + fn read(&mut self, handle: FileHandle, buf: &mut [u8]) -> Option { + let entry = self.files[handle.raw() as usize].as_ref()?; + let len = buf.len().min(entry.size); + buf[..len].copy_from_slice(&entry.data[..len]); + Some(len) + } + + fn write(&mut self, handle: FileHandle, buf: &[u8]) -> Option { + let entry = self.files[handle.raw() as usize].as_mut()?; + let len = buf.len().min(MAX_FILE_SIZE - entry.size); + entry.data[entry.size..entry.size + len].copy_from_slice(&buf[..len]); + entry.size += len; + Some(len) + } + + fn close(&mut self, handle: FileHandle) { + if let Some(entry) = &mut self.files[handle.raw() as usize] { + entry.open = false; + } + } + + fn list_dir(&mut self, _path: &[u8], entries: &mut [DirEntry]) -> Option { + let mut count = 0; + for i in 0..self.count { + if count >= entries.len() { break; } + if let Some(file) = &self.files[i] { + let mut entry = DirEntry::empty(); + let len = file.name_len as usize; + entry.name[..len].copy_from_slice(&file.name[..len]); + entry.name_len = file.name_len; + entry.size = file.size as u32; + entry.is_dir = false; + entries[count] = entry; + count += 1; + } + } + Some(count) + } + + fn create(&mut self, path: &[u8]) -> Option { + if self.count >= MAX_FILES || path.len() > 32 { + return None; + } + let idx = self.count; + let mut entry = FileEntry::empty(); + entry.name[..path.len()].copy_from_slice(path); + entry.name_len = path.len() as u8; + entry.open = true; + self.files[idx] = Some(entry); + self.count += 1; + Some(FileHandle::new(idx as u16)) + } + + fn delete(&mut self, path: &[u8]) -> bool { + if let Some(idx) = self.find_by_name(path) { + self.files[idx] = None; + true + } else { + false + } + } + + fn file_size(&mut self, handle: FileHandle) -> Option { + self.files[handle.raw() as usize].as_ref().map(|e| e.size) + } +} diff --git a/crates/servers/storage-server/src/adapters/mod.rs b/crates/servers/storage-server/src/adapters/mod.rs new file mode 100644 index 0000000..4eb8f1b --- /dev/null +++ b/crates/servers/storage-server/src/adapters/mod.rs @@ -0,0 +1,2 @@ +pub mod mock_block; +pub mod mock_fs; diff --git a/crates/servers/storage-server/src/block_device.rs b/crates/servers/storage-server/src/block_device.rs new file mode 100644 index 0000000..adbbfb7 --- /dev/null +++ b/crates/servers/storage-server/src/block_device.rs @@ -0,0 +1,6 @@ +pub trait BlockDevice { + fn sector_size(&self) -> usize; + fn sector_count(&self) -> usize; + fn read_sector(&mut self, sector: u32, buf: &mut [u8]) -> bool; + fn write_sector(&mut self, sector: u32, buf: &[u8]) -> bool; +} diff --git a/crates/servers/storage-server/src/filesystem.rs b/crates/servers/storage-server/src/filesystem.rs new file mode 100644 index 0000000..773f96d --- /dev/null +++ b/crates/servers/storage-server/src/filesystem.rs @@ -0,0 +1,46 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FileHandle(u16); + +impl FileHandle { + pub const fn new(id: u16) -> Self { + Self(id) + } + + pub const fn raw(&self) -> u16 { + self.0 + } +} + +#[derive(Debug, Clone, Copy)] +pub struct DirEntry { + pub name: [u8; 32], + pub name_len: u8, + pub size: u32, + pub is_dir: bool, +} + +impl DirEntry { + pub fn empty() -> Self { + Self { + name: [0; 32], + name_len: 0, + size: 0, + is_dir: false, + } + } + + pub fn name_bytes(&self) -> &[u8] { + &self.name[..self.name_len as usize] + } +} + +pub trait FileSystem { + fn open(&mut self, path: &[u8]) -> Option; + fn read(&mut self, handle: FileHandle, buf: &mut [u8]) -> Option; + fn write(&mut self, handle: FileHandle, buf: &[u8]) -> Option; + fn close(&mut self, handle: FileHandle); + fn list_dir(&mut self, path: &[u8], entries: &mut [DirEntry]) -> Option; + fn create(&mut self, path: &[u8]) -> Option; + fn delete(&mut self, path: &[u8]) -> bool; + fn file_size(&mut self, handle: FileHandle) -> Option; +} diff --git a/crates/servers/storage-server/src/fs/mod.rs b/crates/servers/storage-server/src/fs/mod.rs new file mode 100644 index 0000000..4338377 --- /dev/null +++ b/crates/servers/storage-server/src/fs/mod.rs @@ -0,0 +1,3 @@ +// Filesystem implementations go here. +// Each implements FileSystem using a BlockDevice. +// e.g., fat16.rs: pub struct Fat16 { ... } diff --git a/crates/servers/storage-server/src/lib.rs b/crates/servers/storage-server/src/lib.rs index b93cf3f..0b9cbd8 100644 --- a/crates/servers/storage-server/src/lib.rs +++ b/crates/servers/storage-server/src/lib.rs @@ -1,14 +1,12 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} +#![no_std] + +pub mod adapters; +pub mod block_device; +pub mod filesystem; +pub mod fs; +pub mod protocol; +pub mod server; #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +#[path = "tests/mod.rs"] +mod tests; diff --git a/crates/servers/storage-server/src/protocol.rs b/crates/servers/storage-server/src/protocol.rs new file mode 100644 index 0000000..267de0b --- /dev/null +++ b/crates/servers/storage-server/src/protocol.rs @@ -0,0 +1,88 @@ +use server_framework::{Decode, Encode}; +use crate::filesystem::FileHandle; + +#[derive(Debug, PartialEq)] +pub enum StorageRequest { + Open { path_word0: usize, path_word1: usize }, + Read { handle: FileHandle, max_bytes: usize }, + Write { handle: FileHandle, data_word: usize }, + Close { handle: FileHandle }, + ListDir { path_word0: usize, path_word1: usize }, + Create { path_word0: usize, path_word1: usize }, + Delete { path_word0: usize, path_word1: usize }, + FileSize { handle: FileHandle }, +} + +#[derive(Debug, PartialEq)] +pub enum StorageResponse { + Handle(FileHandle), + Data { bytes_read: usize }, + Written { bytes_written: usize }, + Ok, + DirEntries { count: usize }, + Size(usize), + NotFound, + Error, +} + +impl Encode for StorageRequest { + fn encode(&self) -> [usize; 4] { + match self { + StorageRequest::Open { path_word0, path_word1 } => [0, *path_word0, *path_word1, 0], + StorageRequest::Read { handle, max_bytes } => [1, handle.raw() as usize, *max_bytes, 0], + StorageRequest::Write { handle, data_word } => [2, handle.raw() as usize, *data_word, 0], + StorageRequest::Close { handle } => [3, handle.raw() as usize, 0, 0], + StorageRequest::ListDir { path_word0, path_word1 } => [4, *path_word0, *path_word1, 0], + StorageRequest::Create { path_word0, path_word1 } => [5, *path_word0, *path_word1, 0], + StorageRequest::Delete { path_word0, path_word1 } => [6, *path_word0, *path_word1, 0], + StorageRequest::FileSize { handle } => [7, handle.raw() as usize, 0, 0], + } + } +} + +impl Decode for StorageRequest { + fn decode(words: [usize; 4]) -> Option { + match words[0] { + 0 => Some(StorageRequest::Open { path_word0: words[1], path_word1: words[2] }), + 1 => Some(StorageRequest::Read { handle: FileHandle::new(words[1] as u16), max_bytes: words[2] }), + 2 => Some(StorageRequest::Write { handle: FileHandle::new(words[1] as u16), data_word: words[2] }), + 3 => Some(StorageRequest::Close { handle: FileHandle::new(words[1] as u16) }), + 4 => Some(StorageRequest::ListDir { path_word0: words[1], path_word1: words[2] }), + 5 => Some(StorageRequest::Create { path_word0: words[1], path_word1: words[2] }), + 6 => Some(StorageRequest::Delete { path_word0: words[1], path_word1: words[2] }), + 7 => Some(StorageRequest::FileSize { handle: FileHandle::new(words[1] as u16) }), + _ => None, + } + } +} + +impl Encode for StorageResponse { + fn encode(&self) -> [usize; 4] { + match self { + StorageResponse::Handle(h) => [0, h.raw() as usize, 0, 0], + StorageResponse::Data { bytes_read } => [1, *bytes_read, 0, 0], + StorageResponse::Written { bytes_written } => [2, *bytes_written, 0, 0], + StorageResponse::Ok => [3, 0, 0, 0], + StorageResponse::DirEntries { count } => [4, *count, 0, 0], + StorageResponse::Size(s) => [5, *s, 0, 0], + StorageResponse::NotFound => [6, 0, 0, 0], + StorageResponse::Error => [7, 0, 0, 0], + } + } +} + +impl Decode for StorageResponse { + fn decode(words: [usize; 4]) -> Option { + match words[0] { + 0 => Some(StorageResponse::Handle(FileHandle::new(words[1] as u16))), + 1 => Some(StorageResponse::Data { bytes_read: words[1] }), + 2 => Some(StorageResponse::Written { bytes_written: words[1] }), + 3 => Some(StorageResponse::Ok), + 4 => Some(StorageResponse::DirEntries { count: words[1] }), + 5 => Some(StorageResponse::Size(words[1])), + 6 => Some(StorageResponse::NotFound), + 7 => Some(StorageResponse::Error), + _ => None, + } + } +} diff --git a/crates/servers/storage-server/src/server.rs b/crates/servers/storage-server/src/server.rs new file mode 100644 index 0000000..85039f9 --- /dev/null +++ b/crates/servers/storage-server/src/server.rs @@ -0,0 +1,100 @@ +use server_framework::Server; + +use crate::filesystem::{FileHandle, FileSystem}; +use crate::protocol::{StorageRequest, StorageResponse}; + +pub struct StorageServer { + fs: F, +} + +impl StorageServer { + pub fn new(fs: F) -> Self { + Self { fs } + } + + pub fn filesystem(&self) -> &F { + &self.fs + } + + fn path_from_words(w0: usize, w1: usize) -> [u8; 16] { + let mut path = [0u8; 16]; + let b0 = w0.to_le_bytes(); + let b1 = w1.to_le_bytes(); + path[..8].copy_from_slice(&b0); + path[8..16].copy_from_slice(&b1); + path + } + + fn path_len(path: &[u8]) -> usize { + path.iter().position(|&b| b == 0).unwrap_or(path.len()) + } +} + +impl Server for StorageServer { + type Request = StorageRequest; + type Response = StorageResponse; + + fn handle(&mut self, request: StorageRequest) -> StorageResponse { + match request { + StorageRequest::Open { path_word0, path_word1 } => { + let path = Self::path_from_words(path_word0, path_word1); + let len = Self::path_len(&path); + match self.fs.open(&path[..len]) { + Some(h) => StorageResponse::Handle(h), + None => StorageResponse::NotFound, + } + } + StorageRequest::Read { handle, max_bytes } => { + let mut buf = [0u8; 512]; + let read_len = max_bytes.min(buf.len()); + match self.fs.read(handle, &mut buf[..read_len]) { + Some(n) => StorageResponse::Data { bytes_read: n }, + None => StorageResponse::Error, + } + } + StorageRequest::Write { handle, data_word } => { + let data = data_word.to_le_bytes(); + match self.fs.write(handle, &data) { + Some(n) => StorageResponse::Written { bytes_written: n }, + None => StorageResponse::Error, + } + } + StorageRequest::Close { handle } => { + self.fs.close(handle); + StorageResponse::Ok + } + StorageRequest::ListDir { path_word0, path_word1 } => { + let path = Self::path_from_words(path_word0, path_word1); + let len = Self::path_len(&path); + let mut entries = [crate::filesystem::DirEntry::empty(); 16]; + match self.fs.list_dir(&path[..len], &mut entries) { + Some(count) => StorageResponse::DirEntries { count }, + None => StorageResponse::NotFound, + } + } + StorageRequest::Create { path_word0, path_word1 } => { + let path = Self::path_from_words(path_word0, path_word1); + let len = Self::path_len(&path); + match self.fs.create(&path[..len]) { + Some(h) => StorageResponse::Handle(h), + None => StorageResponse::Error, + } + } + StorageRequest::Delete { path_word0, path_word1 } => { + let path = Self::path_from_words(path_word0, path_word1); + let len = Self::path_len(&path); + if self.fs.delete(&path[..len]) { + StorageResponse::Ok + } else { + StorageResponse::NotFound + } + } + StorageRequest::FileSize { handle } => { + match self.fs.file_size(handle) { + Some(size) => StorageResponse::Size(size), + None => StorageResponse::Error, + } + } + } + } +} diff --git a/crates/servers/storage-server/src/tests/mod.rs b/crates/servers/storage-server/src/tests/mod.rs new file mode 100644 index 0000000..aa77244 --- /dev/null +++ b/crates/servers/storage-server/src/tests/mod.rs @@ -0,0 +1,2 @@ +mod protocol; +mod server; diff --git a/crates/servers/storage-server/src/tests/protocol.rs b/crates/servers/storage-server/src/tests/protocol.rs new file mode 100644 index 0000000..93c3467 --- /dev/null +++ b/crates/servers/storage-server/src/tests/protocol.rs @@ -0,0 +1,64 @@ +use crate::filesystem::FileHandle; +use crate::protocol::*; +use server_framework::{Decode, Encode}; + +#[test] +fn open_roundtrip() { + let req = StorageRequest::Open { path_word0: 0x61626364, path_word1: 0 }; + assert_eq!(StorageRequest::decode(req.encode()), Some(req)); +} + +#[test] +fn read_roundtrip() { + let req = StorageRequest::Read { handle: FileHandle::new(3), max_bytes: 512 }; + assert_eq!(StorageRequest::decode(req.encode()), Some(req)); +} + +#[test] +fn write_roundtrip() { + let req = StorageRequest::Write { handle: FileHandle::new(1), data_word: 42 }; + assert_eq!(StorageRequest::decode(req.encode()), Some(req)); +} + +#[test] +fn close_roundtrip() { + let req = StorageRequest::Close { handle: FileHandle::new(5) }; + assert_eq!(StorageRequest::decode(req.encode()), Some(req)); +} + +#[test] +fn file_size_roundtrip() { + let req = StorageRequest::FileSize { handle: FileHandle::new(2) }; + assert_eq!(StorageRequest::decode(req.encode()), Some(req)); +} + +#[test] +fn handle_response_roundtrip() { + let resp = StorageResponse::Handle(FileHandle::new(7)); + assert_eq!(StorageResponse::decode(resp.encode()), Some(resp)); +} + +#[test] +fn data_response_roundtrip() { + let resp = StorageResponse::Data { bytes_read: 256 }; + assert_eq!(StorageResponse::decode(resp.encode()), Some(resp)); +} + +#[test] +fn all_response_variants() { + let variants = [ + StorageResponse::Ok, + StorageResponse::NotFound, + StorageResponse::Error, + StorageResponse::Size(1024), + StorageResponse::DirEntries { count: 3 }, + ]; + for resp in variants { + assert_eq!(StorageResponse::decode(resp.encode()), Some(resp)); + } +} + +#[test] +fn invalid_request_returns_none() { + assert!(StorageRequest::decode([99, 0, 0, 0]).is_none()); +} diff --git a/crates/servers/storage-server/src/tests/server.rs b/crates/servers/storage-server/src/tests/server.rs new file mode 100644 index 0000000..845e397 --- /dev/null +++ b/crates/servers/storage-server/src/tests/server.rs @@ -0,0 +1,95 @@ +use crate::adapters::mock_fs::MockFileSystem; +use crate::filesystem::FileHandle; +use crate::protocol::*; +use crate::server::StorageServer; +use server_framework::Server; + +fn test_server() -> StorageServer { + StorageServer::new(MockFileSystem::new()) +} + +fn path_words(s: &[u8]) -> (usize, usize) { + let mut w0 = [0u8; 8]; + let mut w1 = [0u8; 8]; + for (i, &b) in s.iter().enumerate() { + if i < 8 { w0[i] = b; } + else if i < 16 { w1[i - 8] = b; } + } + (usize::from_le_bytes(w0), usize::from_le_bytes(w1)) +} + +#[test] +fn create_and_open() { + let mut server = test_server(); + let (w0, w1) = path_words(b"test.txt"); + + let resp = server.handle(StorageRequest::Create { path_word0: w0, path_word1: w1 }); + assert!(matches!(resp, StorageResponse::Handle(_))); + + let resp = server.handle(StorageRequest::Open { path_word0: w0, path_word1: w1 }); + assert!(matches!(resp, StorageResponse::Handle(_))); +} + +#[test] +fn open_nonexistent_returns_not_found() { + let mut server = test_server(); + let (w0, w1) = path_words(b"nope.txt"); + let resp = server.handle(StorageRequest::Open { path_word0: w0, path_word1: w1 }); + assert_eq!(resp, StorageResponse::NotFound); +} + +#[test] +fn write_then_read() { + let mut server = test_server(); + let (w0, w1) = path_words(b"data"); + + let handle = match server.handle(StorageRequest::Create { path_word0: w0, path_word1: w1 }) { + StorageResponse::Handle(h) => h, + _ => panic!("expected handle"), + }; + + let resp = server.handle(StorageRequest::Write { handle, data_word: 0xDEADBEEF }); + assert!(matches!(resp, StorageResponse::Written { bytes_written: 8 })); + + let resp = server.handle(StorageRequest::FileSize { handle }); + assert_eq!(resp, StorageResponse::Size(8)); +} + +#[test] +fn delete_file() { + let mut server = test_server(); + let (w0, w1) = path_words(b"del.txt"); + + server.handle(StorageRequest::Create { path_word0: w0, path_word1: w1 }); + let resp = server.handle(StorageRequest::Delete { path_word0: w0, path_word1: w1 }); + assert_eq!(resp, StorageResponse::Ok); + + let resp = server.handle(StorageRequest::Open { path_word0: w0, path_word1: w1 }); + assert_eq!(resp, StorageResponse::NotFound); +} + +#[test] +fn list_dir() { + let mut server = test_server(); + let (w0a, w1a) = path_words(b"a.txt"); + let (w0b, w1b) = path_words(b"b.txt"); + + server.handle(StorageRequest::Create { path_word0: w0a, path_word1: w1a }); + server.handle(StorageRequest::Create { path_word0: w0b, path_word1: w1b }); + + let (w0r, w1r) = path_words(b"/"); + let resp = server.handle(StorageRequest::ListDir { path_word0: w0r, path_word1: w1r }); + assert_eq!(resp, StorageResponse::DirEntries { count: 2 }); +} + +#[test] +fn close_succeeds() { + let mut server = test_server(); + let (w0, w1) = path_words(b"c.txt"); + let handle = match server.handle(StorageRequest::Create { path_word0: w0, path_word1: w1 }) { + StorageResponse::Handle(h) => h, + _ => panic!("expected handle"), + }; + let resp = server.handle(StorageRequest::Close { handle }); + assert_eq!(resp, StorageResponse::Ok); +}