diff --git a/Cargo.toml b/Cargo.toml index 60a8cf5b..5ca75638 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ shared = { workspace = true, optional = true } synchronization = { workspace = true, optional = true } host_bindings = { workspace = true, optional = true } bootsplash = { workspace = true, optional = true } +network = { workspace = true, optional = true } [features] default = [] @@ -68,6 +69,7 @@ host = [ "dep:synchronization", "dep:bootsplash", "abi_definitions", + "dep:network", ] abi_definitions = ["dep:abi_definitions"] virtual_machine = ["dep:virtual_machine", "dep:host_bindings"] @@ -138,7 +140,7 @@ embassy-futures = { version = "0.1" } embassy-sync = { version = "0.7" } embassy-time = { version = "0.5" } critical-section = { version = "1.2" } -embassy-net = { version = "0.7", default-features = false } +smoltcp = { version = "0.12" } linked_list_allocator = { version = "0.10", default-features = false, features = [ "const_mut_refs", ] } diff --git a/drivers/native/src/devices/window_screen/window.rs b/drivers/native/src/devices/window_screen/window.rs index 640c6889..e0f185cb 100644 --- a/drivers/native/src/devices/window_screen/window.rs +++ b/drivers/native/src/devices/window_screen/window.rs @@ -40,7 +40,7 @@ impl<'a> Window<'a> { } pub fn paste(&mut self) -> Option<()> { - if let Some(text) = self.clipboard.get_text().ok() { + if let Ok(text) = self.clipboard.get_text() { for character in text.chars() { let key = Key::Character(character as u8); diff --git a/drivers/shared/src/devices/hash.rs b/drivers/shared/src/devices/hash.rs index 6e420dca..7cbfe281 100644 --- a/drivers/shared/src/devices/hash.rs +++ b/drivers/shared/src/devices/hash.rs @@ -46,7 +46,7 @@ impl BaseOperations for HashDevice { fn read(&self, context: &mut Context, buffer: &mut [u8], _: Size) -> Result { let hash_context = context .get_private_data_mutable_of_type::() - .ok_or_else(|| file_system::Error::InvalidParameter)?; + .ok_or(file_system::Error::InvalidParameter)?; if buffer.len() < hash_context.hasher.output_size() { return Err(Error::InvalidParameter); @@ -61,7 +61,7 @@ impl BaseOperations for HashDevice { fn write(&self, context: &mut Context, buffer: &[u8], _: Size) -> Result { let hash_context = context .get_private_data_mutable_of_type::() - .ok_or_else(|| file_system::Error::InvalidParameter)?; + .ok_or(file_system::Error::InvalidParameter)?; hash_context.hasher.update(buffer); Ok(buffer.len()) @@ -76,7 +76,7 @@ impl BaseOperations for HashDevice { ) -> Result<()> { let hash_context = context .get_private_data_mutable_of_type::() - .ok_or_else(|| file_system::Error::InvalidParameter)?; + .ok_or(file_system::Error::InvalidParameter)?; match command { hash::RESET::IDENTIFIER => { @@ -96,7 +96,7 @@ impl BaseOperations for HashDevice { fn clone_context(&self, context: &Context) -> Result { let hash_context = context .get_private_data_of_type::() - .ok_or_else(|| file_system::Error::InvalidParameter)?; + .ok_or(file_system::Error::InvalidParameter)?; Ok(Context::new(Some(hash_context.clone()))) } diff --git a/drivers/std/Cargo.toml b/drivers/std/Cargo.toml index 95a7a6fd..06f01986 100644 --- a/drivers/std/Cargo.toml +++ b/drivers/std/Cargo.toml @@ -23,6 +23,7 @@ embassy-executor = { workspace = true, default-features = false, features = [ "arch-std", "executor-thread", ] } +smoltcp = { workspace = true, features = ["phy-tuntap_interface"] } [dev-dependencies] little_fs = { workspace = true } diff --git a/drivers/std/src/io.rs b/drivers/std/src/io.rs index ec58badf..d3491844 100644 --- a/drivers/std/src/io.rs +++ b/drivers/std/src/io.rs @@ -5,7 +5,7 @@ pub fn map_error(error: io::Error) -> file_system::Error { io::ErrorKind::PermissionDenied => file_system::Error::PermissionDenied, io::ErrorKind::NotFound => file_system::Error::NotFound, io::ErrorKind::AlreadyExists => file_system::Error::AlreadyExists, - io::ErrorKind::InvalidInput => file_system::Error::InvalidPath, + io::ErrorKind::InvalidInput => file_system::Error::InvalidParameter, io::ErrorKind::InvalidData => file_system::Error::InvalidFile, _ => file_system::Error::Unknown, } diff --git a/drivers/std/src/lib.rs b/drivers/std/src/lib.rs index 16dfd309..2f283033 100644 --- a/drivers/std/src/lib.rs +++ b/drivers/std/src/lib.rs @@ -1,22 +1,14 @@ #![cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))] -pub mod network; - -pub mod memory; - -pub mod io; - -pub mod executor; - -pub mod log; - -pub mod loader; - -pub mod drive_file; - pub mod console; - pub mod devices; +pub mod drive_file; +pub mod executor; +pub mod io; +pub mod loader; +pub mod log; +pub mod memory; +pub mod tuntap; pub extern crate memory as memory_exported; diff --git a/drivers/std/src/loader.rs b/drivers/std/src/loader.rs index 2bf8317d..4497fe74 100644 --- a/drivers/std/src/loader.rs +++ b/drivers/std/src/loader.rs @@ -24,8 +24,8 @@ impl From for Error { pub type Result = core::result::Result; -pub async fn load_to_virtual_file_system<'a>( - virtual_file_system: &'a VirtualFileSystem<'a>, +pub async fn load_to_virtual_file_system( + virtual_file_system: &VirtualFileSystem, source_path: impl AsRef, destination_path: impl AsRef, ) -> Result<()> { @@ -89,7 +89,6 @@ mod tests { users::get_instance(), time::get_instance(), file_system, - None, ) .unwrap(); diff --git a/drivers/std/src/memory.rs b/drivers/std/src/memory.rs index e173d072..34c833d8 100644 --- a/drivers/std/src/memory.rs +++ b/drivers/std/src/memory.rs @@ -26,6 +26,12 @@ pub struct MemoryManager { regions: CriticalSectionMutex>, } +impl Default for MemoryManager { + fn default() -> Self { + Self::new() + } +} + impl MemoryManager { pub const fn new() -> Self { MemoryManager { diff --git a/drivers/std/src/network/error.rs b/drivers/std/src/network/error.rs deleted file mode 100644 index 443cf872..00000000 --- a/drivers/std/src/network/error.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::io::{self, ErrorKind}; - -use network::Error; - -pub fn into_socket_error(error: io::Error) -> Error { - match error.kind() { - ErrorKind::NotFound => Error::NotFound, - ErrorKind::PermissionDenied => Error::PermissionDenied, - ErrorKind::ConnectionRefused => Error::ConnectionRefused, - ErrorKind::ConnectionReset => Error::ConnectionReset, - ErrorKind::ConnectionAborted => Error::ConnectionAborted, - ErrorKind::HostUnreachable => Error::HostUnreachable, - ErrorKind::NetworkUnreachable => Error::NetworkUnreachable, - ErrorKind::NotConnected => Error::NotConnected, - ErrorKind::AddrInUse => Error::AddressInUse, - ErrorKind::AddrNotAvailable => Error::AddressNotAvailable, - ErrorKind::NetworkDown => Error::NetworkDown, - ErrorKind::BrokenPipe => Error::BrokenPipe, - ErrorKind::AlreadyExists => Error::AlreadyExists, - ErrorKind::WouldBlock => Error::WouldBlock, - - // ErrorKind::FilesystemLoop => Error::Filesystem_loop, - ErrorKind::InvalidInput => Error::InvalidInput, - ErrorKind::InvalidData => Error::InvalidData, - ErrorKind::TimedOut => Error::TimedOut, - ErrorKind::WriteZero => Error::WriteZero, - ErrorKind::StorageFull => Error::StorageFull, - - // ErrorKind::FilesystemQuotaExceeded => Error::Filesystem_quota_exceeded, - ErrorKind::ResourceBusy => Error::ResourceBusy, - - ErrorKind::Deadlock => Error::Deadlock, - // ErrorKind::CrossesDevices => todo!(), - - // ErrorKind::InvalidFilename => todo!(), - ErrorKind::ArgumentListTooLong => todo!(), - ErrorKind::Interrupted => Error::Interrupted, - ErrorKind::Unsupported => Error::Unsupported, - ErrorKind::UnexpectedEof => Error::UnexpectedEndOfFile, - ErrorKind::OutOfMemory => Error::OutOfMemory, - // ErrorKind::InProgress => todo!(), - ErrorKind::Other => Error::Other, - _ => todo!(), - } -} diff --git a/drivers/std/src/network/mod.rs b/drivers/std/src/network/mod.rs deleted file mode 100644 index b7e53371..00000000 --- a/drivers/std/src/network/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod error; -mod resolver; - -pub use error::*; -pub use resolver::*; diff --git a/drivers/std/src/network/resolver.rs b/drivers/std/src/network/resolver.rs deleted file mode 100644 index f997d16b..00000000 --- a/drivers/std/src/network/resolver.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub struct Resolver; - -impl Default for Resolver { - fn default() -> Self { - Self::new() - } -} - -impl Resolver { - pub fn new() -> Self { - Self - } -} diff --git a/drivers/std/src/tuntap/controller.rs b/drivers/std/src/tuntap/controller.rs new file mode 100644 index 00000000..aa0124d1 --- /dev/null +++ b/drivers/std/src/tuntap/controller.rs @@ -0,0 +1,38 @@ +use file_system::{ + ControlCommand, ControlCommandIdentifier, DirectBaseOperations, DirectCharacterDevice, Error, + MountOperations, +}; +use network::{GET_KIND, InterfaceKind}; +use shared::AnyByLayout; + +pub struct TunTapControllerDevice; + +impl DirectBaseOperations for TunTapControllerDevice { + fn read(&self, _: &mut [u8], _: file_system::Size) -> file_system::Result { + Err(Error::UnsupportedOperation) + } + + fn write(&self, _: &[u8], _: file_system::Size) -> file_system::Result { + Err(Error::UnsupportedOperation) + } + + fn control( + &self, + command: ControlCommandIdentifier, + _: &AnyByLayout, + output: &mut AnyByLayout, + ) -> file_system::Result<()> { + match command { + GET_KIND::IDENTIFIER => { + let kind = GET_KIND::cast_output(output)?; + *kind = InterfaceKind::Ethernet; + Ok(()) + } + _ => Err(Error::UnsupportedOperation), + } + } +} + +impl MountOperations for TunTapControllerDevice {} + +impl DirectCharacterDevice for TunTapControllerDevice {} diff --git a/drivers/std/src/tuntap/mod.rs b/drivers/std/src/tuntap/mod.rs new file mode 100644 index 00000000..1b4bcb1e --- /dev/null +++ b/drivers/std/src/tuntap/mod.rs @@ -0,0 +1,158 @@ +mod controller; + +pub use controller::*; + +use network::{IpAddress, IpCidr, Route}; +use smoltcp::phy::{Medium, TunTapInterface}; +use std::process::Command; + +pub const IP_ADDRESSES: &[IpCidr] = &[ + IpCidr::new_ipv4([192, 168, 69, 1], 24), + IpCidr::new_ipv4([172, 0, 0, 1], 8), + IpCidr::new_ipv6([0xfdaa, 0, 0, 0, 0, 0, 0, 1], 64), + IpCidr::new_ipv6([0xfe80, 0, 0, 0, 0, 0, 0, 1], 64), +]; + +pub const ROUTES: &[Route] = &[ + Route::new_default_ipv4([192, 168, 69, 100]), + Route::new_default_ipv6([0xfe80, 0, 0, 0, 0, 0, 0, 100]), +]; + +pub const DEFAULT_DNS_SERVERS: &[IpAddress] = &[ + IpAddress::new_ipv4([1, 1, 1, 1]), + IpAddress::new_ipv4([1, 0, 0, 1]), + IpAddress::new_ipv6([0x2606, 0x4700, 0x4700, 0, 0, 0, 0, 0x1111]), + IpAddress::new_ipv6([0x2606, 0x4700, 0x4700, 0, 0, 0, 0, 0x1001]), +]; + +fn interface_exists(name: &str) -> bool { + std::fs::metadata(format!("/sys/class/net/{}", name)).is_ok() +} + +fn setup_tap_interface(name: &str) -> Result<(), String> { + // Get the user from SUDO_USER environment variable + log::information!( + "Setting up TAP interface: {} (sudo permissions required)", + name + ); + + let user = std::env::var("SUDO_USER") + .unwrap_or_else(|_| std::env::var("USER").unwrap_or_else(|_| "root".to_string())); + + log::information!("Using user: {}", user); + + let commands: &[&[&str]] = &[ + &[ + "ip", "tuntap", "add", "name", name, "mode", "tap", "user", &user, + ], + &["ip", "link", "set", name, "up"], + &["ip", "addr", "add", "192.168.69.100/24", "dev", name], + &["ip", "-6", "addr", "add", "fe80::100/64", "dev", name], + &["ip", "-6", "addr", "add", "fdaa::100/64", "dev", name], + &[ + "iptables", + "-t", + "nat", + "-I", // Change -A to -I + "POSTROUTING", + "1", // Insert at position 1 + "-s", + "192.168.69.0/24", + "-j", + "MASQUERADE", + ], + &[ + "iptables", + "-I", // Change -A to -I + "FORWARD", + "1", // Insert at position 1 + "-i", + name, + "-s", + "192.168.69.0/24", + "-j", + "ACCEPT", + ], + &[ + "iptables", + "-I", // Change -A to -I + "FORWARD", + "1", // Insert at position 1 + "-o", + name, + "-d", + "192.168.69.0/24", + "-j", + "ACCEPT", + ], + &["sysctl", "-w", "net.ipv4.ip_forward=1"], + ]; + + // Commands that can fail if already exist (routes) + let optional_commands: &[&[&str]] = &[ + &["ip", "-6", "route", "add", "fe80::/64", "dev", name], + &["ip", "-6", "route", "add", "fdaa::/64", "dev", name], + ]; + + for &cmd_args in commands { + let output = Command::new("sudo") + .args(cmd_args) + .output() + .map_err(|e| format!("Failed to execute command: {}", e))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!( + "Command 'sudo {}' failed: {}", + cmd_args.join(" "), + stderr + )); + } + } + + // Execute optional commands (ignore "File exists" errors) + for &cmd_args in optional_commands { + let output = Command::new("sudo") + .args(cmd_args) + .output() + .map_err(|e| format!("Failed to execute command: {}", e))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + // Ignore "File exists" errors for routes + if !stderr.contains("File exists") { + log::warning!("Command 'sudo {}' failed: {}", cmd_args.join(" "), stderr); + } + } + } + + Ok(()) +} + +pub fn new(name: &str, tun: bool, tap: bool) -> Option<(TunTapInterface, TunTapControllerDevice)> { + let medium = if tun { + Medium::Ip + } else if tap { + Medium::Ethernet + } else { + log::error!("Either TUN or TAP mode must be specified."); + return None; + }; + + if !interface_exists(name) { + if let Err(e) = setup_tap_interface(name) { + log::error!("Failed to setup TAP interface: {}", e); + return None; + } + } else { + log::information!("TAP interface {} already exists.", name); + } + + let tuntap_device = TunTapInterface::new(name, medium) + .map_err(|e| log::error!("Failed to create TUN/TAP device: {}", e)) + .ok()?; + + let controller = TunTapControllerDevice {}; + + Some((tuntap_device, controller)) +} diff --git a/examples/native/src/main.rs b/examples/native/src/main.rs index 8c041389..1eb95156 100644 --- a/examples/native/src/main.rs +++ b/examples/native/src/main.rs @@ -15,6 +15,7 @@ async fn main() { use xila::executable::Standard; use xila::executable::build_crate; use xila::executable::mount_executables; + use xila::file_system::AccessFlags; use xila::file_system::XILA_DISK_SIGNATURE; use xila::file_system::mbr::Mbr; use xila::file_system::mbr::PartitionKind; @@ -22,10 +23,12 @@ async fn main() { use xila::host_bindings; use xila::little_fs; use xila::log; + use xila::network::{self, ADD_DNS_SERVER, ADD_IP_ADDRESS, ADD_ROUTE}; use xila::task; use xila::time; use xila::users; use xila::virtual_file_system; + use xila::virtual_file_system::File; use xila::virtual_file_system::mount_static; use xila::virtual_machine; @@ -108,14 +111,9 @@ async fn main() { let file_system = little_fs::FileSystem::get_or_format(partition, 256).unwrap(); // Initialize the virtual file system - let virtual_file_system = virtual_file_system::initialize( - task_manager, - users_manager, - time_manager, - file_system, - None, - ) - .unwrap(); + let virtual_file_system = + virtual_file_system::initialize(task_manager, users_manager, time_manager, file_system) + .unwrap(); log::information!("Virtual file system initialized."); @@ -166,7 +164,42 @@ async fn main() { .await .unwrap(); - log::information!("Devices mounted."); + let (interface_device, controller_device) = drivers_std::tuntap::new("xila0", false, true) + .expect("Failed to create network interface."); + + let network_manager = network::initialize( + task_manager, + virtual_file_system, + &drivers_shared::devices::RandomDevice, + ); + + network_manager + .mount_interface(task, "tunnel0", interface_device, controller_device, None) + .await + .expect("Failed to mount network interface."); + + let mut file = File::open( + virtual_file_system, + task, + "/devices/network/tunnel0", + AccessFlags::READ_WRITE.into(), + ) + .await + .expect("Failed to open network interface file."); + + for ip_cidr in drivers_std::tuntap::IP_ADDRESSES { + file.control(ADD_IP_ADDRESS, ip_cidr).await.ok(); + } + + for route in drivers_std::tuntap::ROUTES { + file.control(ADD_ROUTE, route).await.ok(); + } + + for dns_server in drivers_std::tuntap::DEFAULT_DNS_SERVERS { + file.control(ADD_DNS_SERVER, dns_server).await.ok(); + } + + file.close(virtual_file_system).await.unwrap(); // Initialize the virtual machine virtual_machine::initialize(&[&host_bindings::GraphicsBindings]); @@ -180,8 +213,6 @@ async fn main() { Box::pin(new_thread_executor()) } - log::information!("Mounting executables..."); - mount_executables!( virtual_file_system, task, diff --git a/examples/wasm/src/main.rs b/examples/wasm/src/main.rs index 2cbfe67f..9edd1580 100644 --- a/examples/wasm/src/main.rs +++ b/examples/wasm/src/main.rs @@ -93,14 +93,9 @@ async fn main() { let file_system = little_fs::FileSystem::get_or_format(partition, 256).unwrap(); // Initialize the virtual file system - let virtual_file_system = virtual_file_system::initialize( - task_manager, - users_manager, - time_manager, - file_system, - None, - ) - .unwrap(); + let virtual_file_system = + virtual_file_system::initialize(task_manager, users_manager, time_manager, file_system) + .unwrap(); // - - Mount the devices diff --git a/executables/calculator/tests/test.rs b/executables/calculator/tests/test.rs index da27e21a..1123071a 100644 --- a/executables/calculator/tests/test.rs +++ b/executables/calculator/tests/test.rs @@ -17,7 +17,7 @@ async fn main() { let binary_path = build_crate("calculator").unwrap(); let binary_buffer = fs::read(binary_path).unwrap(); - let standard = testing::initialize(true).await.split(); + let standard = testing::initialize(true, false).await.split(); let task_manager = task::get_instance(); let virtual_machine = virtual_machine::initialize(&[&host_bindings::GraphicsBindings]); diff --git a/executables/file_manager/src/file_manager.rs b/executables/file_manager/src/file_manager.rs index 49e2a2e9..ea934913 100644 --- a/executables/file_manager/src/file_manager.rs +++ b/executables/file_manager/src/file_manager.rs @@ -6,16 +6,15 @@ pub(crate) use alloc::{ vec::Vec, }; use core::ptr::null_mut; -use xila::graphics::{ - self, EventKind, Window, lvgl, - palette::{self, Hue}, -}; use xila::log; use xila::task; use xila::virtual_file_system::{Directory, get_instance}; use xila::{ file_system::{Kind, Path, PathOwned}, - graphics::symbols, + graphics::{ + self, EventKind, Window, lvgl, + palette::{self, Hue}, + }, }; pub struct FileManager { @@ -157,7 +156,7 @@ impl FileManager { return Err(Error::FailedToCreateObject); } let up_label = lvgl::lv_label_create(self.up_button); - lvgl::lv_label_set_text(up_label, symbols::UP.as_ptr()); + lvgl::lv_label_set_text(up_label, lvgl::LV_SYMBOL_UP as *const _ as *const i8); lvgl::lv_obj_center(up_label); // Remove event handler - events bubble up to window @@ -169,7 +168,7 @@ impl FileManager { } let home_label = lvgl::lv_label_create(self.home_button); - lvgl::lv_label_set_text(home_label, symbols::HOME.as_ptr()); + lvgl::lv_label_set_text(home_label, lvgl::LV_SYMBOL_HOME as *const _ as *const i8); lvgl::lv_obj_center(home_label); // Remove event handler - events bubble up to window @@ -182,7 +181,10 @@ impl FileManager { let refresh_label = lvgl::lv_label_create(self.refresh_button); - lvgl::lv_label_set_text(refresh_label, symbols::REFRESH.as_ptr()); + lvgl::lv_label_set_text( + refresh_label, + lvgl::LV_SYMBOL_REFRESH as *const _ as *const i8, + ); lvgl::lv_obj_center(refresh_label); // Remove event handler - events bubble up to window @@ -207,7 +209,7 @@ impl FileManager { } let go_label = lvgl::lv_label_create(self.go_button); - lvgl::lv_label_set_text(go_label, symbols::RIGHT.as_ptr()); + lvgl::lv_label_set_text(go_label, lvgl::LV_SYMBOL_RIGHT as *const _ as *const i8); lvgl::lv_obj_center(go_label); self.update_path_label(); @@ -284,8 +286,8 @@ impl FileManager { let file = &self.files[index]; let icon_symbol = match file.kind { - Kind::Directory => symbols::DIRECTORY, - _ => symbols::FILE, + Kind::Directory => lvgl::LV_SYMBOL_DIRECTORY, + _ => lvgl::LV_SYMBOL_FILE, }; let name_cstring = CString::new(file.name.clone()).unwrap(); diff --git a/executables/file_manager/src/lib.rs b/executables/file_manager/src/lib.rs index 23645f9c..86f23a4f 100644 --- a/executables/file_manager/src/lib.rs +++ b/executables/file_manager/src/lib.rs @@ -30,8 +30,8 @@ pub const SHORTCUT: &str = r#" pub struct FileManagerExecutable; impl FileManagerExecutable { - pub async fn new<'a>( - virtual_file_system: &'a VirtualFileSystem<'a>, + pub async fn new( + virtual_file_system: &VirtualFileSystem, task: TaskIdentifier, ) -> core::result::Result { let _ = virtual_file_system diff --git a/executables/file_manager/tests/integration_test.rs b/executables/file_manager/tests/integration_test.rs index ebf628a7..c5bdb6be 100644 --- a/executables/file_manager/tests/integration_test.rs +++ b/executables/file_manager/tests/integration_test.rs @@ -13,7 +13,7 @@ async fn main() { task, virtual_file_system, }; - let standard = testing::initialize(true).await; + let standard = testing::initialize(true, false).await; mount_executables!( virtual_file_system::get_instance(), diff --git a/executables/settings/locales/en/messages.po b/executables/settings/locales/en/messages.po index 54943a36..0259c9f2 100644 --- a/executables/settings/locales/en/messages.po +++ b/executables/settings/locales/en/messages.po @@ -96,6 +96,12 @@ msgstr "Change Password" msgid "About" msgstr "About" +msgid "Operating System:" +msgstr "Operating System:" + +msgid "Description:" +msgstr "Description:" + msgid "Developed by:" msgstr "Developed by:" @@ -108,5 +114,38 @@ msgstr "Version:" msgid "Locale:" msgstr "Locale:" -msgid "Fallback:" -msgstr "Fallback:" \ No newline at end of file +msgid "Memory:" +msgstr "Memory:" + +msgid "Cancel" +msgstr "Cancel" + +msgid "Apply" +msgstr "Apply" + +msgid "None" +msgstr "None" + +msgid "DHCP" +msgstr "DHCP" + +msgid "Static" +msgstr "Static" + +msgid "Configuration Mode" +msgstr "Configuration Mode" + +msgid "MAC Address" +msgstr "MAC Address" + +msgid "Address" +msgstr "Address" + +msgid "Routes" +msgstr "Routes" + +msgid "Gateway" +msgstr "Gateway" + +msgid "DNS Servers" +msgstr "DNS Servers" diff --git a/executables/settings/locales/fr/messages.po b/executables/settings/locales/fr/messages.po index 14462438..10b5b6be 100644 --- a/executables/settings/locales/fr/messages.po +++ b/executables/settings/locales/fr/messages.po @@ -96,6 +96,12 @@ msgstr "Changer le mot de passe" msgid "About" msgstr "À propos" +msgid "Operating System:" +msgstr "Système d'exploitation :" + +msgid "Description:" +msgstr "Description :" + msgid "Developed by:" msgstr "Développé par :" @@ -108,5 +114,38 @@ msgstr "Version :" msgid "Locale:" msgstr "Langue :" -msgid "Fallback:" -msgstr "De secours :" \ No newline at end of file +msgid "Memory:" +msgstr "Mémoire :" + +msgid "Cancel" +msgstr "Annuler" + +msgid "Apply" +msgstr "Appliquer" + +msgid "None" +msgstr "Aucun" + +msgid "DHCP" +msgstr "DHCP" + +msgid "Static" +msgstr "Statique" + +msgid "Configuration Mode" +msgstr "Mode de configuration" + +msgid "MAC Address" +msgstr "Adresse MAC" + +msgid "Address" +msgstr "Adresse" + +msgid "Routes" +msgstr "Routes" + +msgid "Gateway" +msgstr "Passerelle" + +msgid "DNS Servers" +msgstr "Serveurs DNS" diff --git a/executables/settings/locales/messages.pot b/executables/settings/locales/messages.pot index 220f4cda..192d10d5 100644 --- a/executables/settings/locales/messages.pot +++ b/executables/settings/locales/messages.pot @@ -96,6 +96,12 @@ msgstr "" msgid "About" msgstr "" +msgid "Operating System:" +msgstr "" + +msgid "Description:" +msgstr "" + msgid "Developed by:" msgstr "" @@ -108,5 +114,38 @@ msgstr "" msgid "Locale:" msgstr "" -msgid "Fallback:" -msgstr "" \ No newline at end of file +msgid "Memory:" +msgstr "" + +msgid "Cancel" +msgstr "" + +msgid "Apply" +msgstr "" + +msgid "None" +msgstr "" + +msgid "DHCP" +msgstr "" + +msgid "Static" +msgstr "" + +msgid "Configuration Mode" +msgstr "" + +msgid "MAC Address" +msgstr "" + +msgid "Address" +msgstr "" + +msgid "Routes" +msgstr "" + +msgid "Gateway" +msgstr "" + +msgid "DNS Servers" +msgstr "" diff --git a/executables/settings/src/lib.rs b/executables/settings/src/lib.rs index 3ebfef37..4b1869c5 100644 --- a/executables/settings/src/lib.rs +++ b/executables/settings/src/lib.rs @@ -37,8 +37,8 @@ pub fn get_shortcut() -> alloc::string::String { pub struct SettingsExecutable; impl SettingsExecutable { - pub async fn new<'a>( - virtual_file_system: &'a VirtualFileSystem<'a>, + pub async fn new( + virtual_file_system: &VirtualFileSystem, task: TaskIdentifier, ) -> core::result::Result { let _ = virtual_file_system diff --git a/executables/settings/src/settings.rs b/executables/settings/src/settings.rs index 7b845587..3ddc743a 100644 --- a/executables/settings/src/settings.rs +++ b/executables/settings/src/settings.rs @@ -6,11 +6,11 @@ use xila::graphics::{ }; use crate::error::Result; -use crate::tabs::{AboutTab, GeneralTab, PasswordTab, Tab}; +use crate::tabs::{AboutTab, GeneralTab, NetworkTab, PasswordTab, Tab}; pub struct Settings { window: Window, - tabs: [Tab; 3], + tabs: [Tab; 4], } #[derive(Clone)] @@ -42,12 +42,15 @@ impl Settings { let mut tabs = [ Tab::General(GeneralTab::new()), Tab::Password(PasswordTab::new()), + Tab::Network(NetworkTab::new()), Tab::About(AboutTab::new()), ]; - tabs.iter_mut().for_each(|tab| { - tab.create_ui(tabview).expect("Failed to create tab UI"); - }); + for tab in &mut tabs { + tab.create_ui(tabview) + .await + .expect("Failed to create tab UI"); + } let manager = Self { window, tabs }; diff --git a/executables/settings/src/tabs/about.rs b/executables/settings/src/tabs/about.rs index 1dd75511..a0c8029f 100644 --- a/executables/settings/src/tabs/about.rs +++ b/executables/settings/src/tabs/about.rs @@ -1,36 +1,31 @@ use crate::error::Result; -use alloc::{vec, vec::Vec}; -use core::ptr::null_mut; -use embedded_io::Write as _; +use alloc::{ffi::CString, format}; +use core::{ffi::CStr, ptr::null_mut}; use xila::{ about, graphics::{ Event, - lvgl::{self, lv_pct}, + lvgl::{self}, }, internationalization::{self, translate}, + memory, + shared::{BYTES_SUFFIX, Unit}, }; -const TABLE_ROWS: usize = 6; - pub struct AboutTab { container: *mut lvgl::lv_obj_t, + list: *mut lvgl::lv_obj_t, } impl AboutTab { pub fn new() -> Self { Self { container: null_mut() as *mut _, + list: null_mut() as *mut _, } } - fn str_to_cstring(buffer: &mut Vec, s: &str) -> Result<*const i8> { - buffer.clear(); - write!(buffer, "{}\0", s)?; - Ok(buffer.as_ptr() as *const i8) - } - - pub fn create_ui( + pub async fn create_ui( &mut self, parent_tabview: *mut lvgl::lv_obj_t, ) -> Result<*mut lvgl::lv_obj_t> { @@ -41,76 +36,66 @@ impl AboutTab { return Err(crate::error::Error::FailedToCreateUiElement); } - let table = unsafe { - let table = lvgl::lv_table_create(self.container); + // Create list + unsafe { + self.list = lvgl::lv_list_create(self.container); - if table.is_null() { + if self.list.is_null() { return Err(crate::error::Error::FailedToCreateUiElement); } - lvgl::lv_obj_align(table, lvgl::lv_align_t_LV_ALIGN_CENTER, 0, 0); - lvgl::lv_table_set_row_count(table, TABLE_ROWS as _); - lvgl::lv_table_set_column_count(table, 2); - lvgl::lv_obj_set_height(table, lv_pct(100)); - lvgl::lv_table_set_column_width(table, 0, 100); - lvgl::lv_table_set_column_width(table, 1, 200); + // List properties - fill container + lvgl::lv_obj_set_size(self.list, lvgl::lv_pct(100), lvgl::lv_pct(100)); + lvgl::lv_obj_set_style_pad_all(self.list, 0, lvgl::LV_STATE_DEFAULT); + } + + // Populate items - convert CStr to String + self.create_list_item(translate!(c"Operating System:"), c"Xila")?; + + let description = CString::new(about::get_description()) + .map_err(|_| crate::error::Error::FailedToCreateUiElement)?; + self.create_list_item(translate!(c"Description:"), &description)?; + + let authors = CString::new(about::get_authors()) + .map_err(|_| crate::error::Error::FailedToCreateUiElement)?; + self.create_list_item(translate!(c"Developed by:"), &authors)?; + + let license = CString::new(about::get_license()) + .map_err(|_| crate::error::Error::FailedToCreateUiElement)?; + self.create_list_item(translate!(c"License:"), &license)?; + + let version = CString::new(about::get_version_string()) + .map_err(|_| crate::error::Error::FailedToCreateUiElement)?; + self.create_list_item(translate!(c"Version:"), &version)?; - table - }; + let locale = CString::new(format!( + "{} ({})", + internationalization::get_locale(), + internationalization::get_fallback_locale(), + ))?; + self.create_list_item(translate!(c"Locale:"), &locale)?; + let memory = memory::get_instance().get_total_size(); + let memory = Unit::new(memory as f32, BYTES_SUFFIX.symbol); + let memory = CString::new(format!("{}", memory)) + .map_err(|_| crate::error::Error::FailedToCreateUiElement)?; + self.create_list_item(translate!(c"Memory:"), &memory)?; + + Ok(self.container) + } + + fn create_list_item(&mut self, name: &CStr, value: &CStr) -> Result<()> { unsafe { - let mut buffer = vec![]; - - lvgl::lv_table_set_cell_value(table, 0, 0, c"Xila".as_ptr()); - lvgl::lv_table_set_cell_value( - table, - 0, - 1, - Self::str_to_cstring(&mut buffer, about::get_description())?, - ); - - lvgl::lv_table_set_cell_value(table, 1, 0, translate!(c"Developed by:").as_ptr()); - lvgl::lv_table_set_cell_value( - table, - 1, - 1, - Self::str_to_cstring(&mut buffer, about::get_authors())?, - ); - - lvgl::lv_table_set_cell_value(table, 2, 0, translate!(c"License:").as_ptr()); - lvgl::lv_table_set_cell_value( - table, - 2, - 1, - Self::str_to_cstring(&mut buffer, about::get_license())?, - ); - - lvgl::lv_table_set_cell_value(table, 3, 0, translate!(c"Version:").as_ptr()); - lvgl::lv_table_set_cell_value( - table, - 3, - 1, - Self::str_to_cstring(&mut buffer, about::get_version_string())?, - ); - - lvgl::lv_table_set_cell_value(table, 4, 0, translate!(c"Locale:").as_ptr()); - lvgl::lv_table_set_cell_value( - table, - 4, - 1, - Self::str_to_cstring(&mut buffer, internationalization::get_locale())?, - ); - - lvgl::lv_table_set_cell_value(table, 5, 0, translate!(c"Fallback:").as_ptr()); - lvgl::lv_table_set_cell_value( - table, - 5, - 1, - Self::str_to_cstring(&mut buffer, internationalization::get_fallback_locale())?, - ); + lvgl::lv_list_add_text(self.list, name.as_ptr()); + + let button = lvgl::lv_list_add_button(self.list, core::ptr::null(), value.as_ptr()); + + if button.is_null() { + return Err(crate::error::Error::FailedToCreateUiElement); + } } - Ok(self.container) + Ok(()) } pub async fn handle_event(&mut self, _event: &Event) -> bool { diff --git a/executables/settings/src/tabs/general.rs b/executables/settings/src/tabs/general.rs index b7480c24..efca75a9 100644 --- a/executables/settings/src/tabs/general.rs +++ b/executables/settings/src/tabs/general.rs @@ -13,7 +13,7 @@ impl GeneralTab { } } - pub fn create_ui( + pub async fn create_ui( &mut self, parent_tabview: *mut lvgl::lv_obj_t, ) -> Result<*mut lvgl::lv_obj_t> { diff --git a/executables/settings/src/tabs/mod.rs b/executables/settings/src/tabs/mod.rs index 1cefeceb..0230472a 100644 --- a/executables/settings/src/tabs/mod.rs +++ b/executables/settings/src/tabs/mod.rs @@ -6,14 +6,16 @@ pub enum Tab { General(GeneralTab), Password(PasswordTab), About(AboutTab), + Network(NetworkTab), } impl Tab { - pub fn create_ui(&mut self, parent: *mut lvgl::lv_obj_t) -> Result<*mut lvgl::lv_obj_t> { + pub async fn create_ui(&mut self, parent: *mut lvgl::lv_obj_t) -> Result<*mut lvgl::lv_obj_t> { match self { - Tab::General(tab) => tab.create_ui(parent), - Tab::Password(tab) => tab.create_ui(parent), - Tab::About(tab) => tab.create_ui(parent), + Tab::General(tab) => tab.create_ui(parent).await, + Tab::Password(tab) => tab.create_ui(parent).await, + Tab::About(tab) => tab.create_ui(parent).await, + Tab::Network(tab) => tab.create_ui(parent).await, } } @@ -22,6 +24,7 @@ impl Tab { Tab::General(tab) => tab.handle_event(event).await, Tab::Password(tab) => tab.handle_event(event).await, Tab::About(tab) => tab.handle_event(event).await, + Tab::Network(tab) => tab.handle_event(event).await, } } } @@ -29,8 +32,10 @@ impl Tab { // Re-export tab modules pub mod about; pub mod general; +pub mod network; pub mod password; pub use about::AboutTab; pub use general::GeneralTab; +pub use network::NetworkTab; pub use password::PasswordTab; diff --git a/executables/settings/src/tabs/network/interface.rs b/executables/settings/src/tabs/network/interface.rs new file mode 100644 index 00000000..5f42cdfd --- /dev/null +++ b/executables/settings/src/tabs/network/interface.rs @@ -0,0 +1,392 @@ +use crate::{Error, Result, tabs::network::open_interface}; +use alloc::string::String; +use core::fmt::Write; +use core::ptr::null_mut; +use xila::{ + graphics::{EventKind, lvgl}, + internationalization::translate, + log, + network::{ + GET_DNS_SERVER, GET_DNS_SERVER_COUNT, GET_HARDWARE_ADDRESS, GET_IP_ADDRESS, + GET_IP_ADDRESS_COUNT, GET_ROUTE, GET_ROUTE_COUNT, MacAddress, + }, + virtual_file_system::{self, File, FileControlIterator}, +}; + +#[derive(Default)] +struct IpConfigurationTab { + pub _container: *mut lvgl::lv_obj_t, + pub _radio_group: *mut lvgl::lv_obj_t, + pub _radio_none: *mut lvgl::lv_obj_t, + pub _radio_dhcp: *mut lvgl::lv_obj_t, + pub _radio_static: *mut lvgl::lv_obj_t, + pub _address_input: *mut lvgl::lv_obj_t, + pub _gateway_input: *mut lvgl::lv_obj_t, + pub _dns_inputs: [*mut lvgl::lv_obj_t; 3], +} + +struct GeneralTab { + pub _list: *mut lvgl::lv_obj_t, +} + +pub struct InterfacePanel { + main_container: *mut lvgl::lv_obj_t, + + cancel_button: *mut lvgl::lv_obj_t, + apply_button: *mut lvgl::lv_obj_t, + interface: String, +} + +impl InterfacePanel { + async fn create_general_tab( + parent: *mut lvgl::lv_obj_t, + file: &mut File, + ) -> Result { + unsafe { + let list = lvgl::lv_list_create(parent); + + let mut format_buffer = String::with_capacity(64); + + { + let hardware_address = Self::get_mac_address(file).await?; + + lvgl::lv_obj_set_size(list, lvgl::lv_pct(100), lvgl::lv_pct(100)); + + write!( + format_buffer, + "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}\0", + hardware_address[0], + hardware_address[1], + hardware_address[2], + hardware_address[3], + hardware_address[4], + hardware_address[5] + ) + .ok(); + lvgl::lv_list_add_text(list, translate!(c"MAC Address").as_ptr()); + + lvgl::lv_list_add_button(list, null_mut(), format_buffer.as_ptr() as _); + } + + { + let mut ip_addresses = Self::get_ip_addresses(file).await?; + + lvgl::lv_list_add_text(list, translate!(c"Address").as_ptr()); + + while let Some(ip) = ip_addresses.next().await? { + format_buffer.clear(); + write!(format_buffer, "{}\0", ip).ok(); + + lvgl::lv_list_add_button(list, null_mut(), format_buffer.as_ptr() as _); + } + } + + { + let mut routes = Self::get_routes(file).await?; + + lvgl::lv_list_add_text(list, translate!(c"Routes").as_ptr()); + + while let Some(route) = routes.next().await? { + format_buffer.clear(); + write!(format_buffer, "{} via {}\0", route.cidr, route.via_router).ok(); + lvgl::lv_list_add_button(list, null_mut(), format_buffer.as_ptr() as _); + } + } + + { + let mut dns_servers = Self::get_dns_servers(file).await?; + + lvgl::lv_list_add_text(list, translate!(c"DNS Servers").as_ptr()); + + while let Some(dns) = dns_servers.next().await? { + format_buffer.clear(); + write!(format_buffer, "{}\0", dns).ok(); + lvgl::lv_list_add_button(list, null_mut(), format_buffer.as_ptr() as _); + } + } + + Ok(GeneralTab { _list: list }) + } + } + + fn create_ip_configuration_tab( + parent: *mut lvgl::lv_obj_t, + is_ipv6: bool, + tab_name: &[u8], + ) -> Result { + unsafe { + // Create tab for this IP version + let container = lvgl::lv_tabview_add_tab(parent, tab_name.as_ptr() as *const _); + if container.is_null() { + return Err(Error::FailedToCreateObject); + } + + lvgl::lv_obj_set_size(container, lvgl::lv_pct(100), lvgl::lv_pct(100)); + lvgl::lv_obj_set_flex_flow(container, lvgl::lv_flex_flow_t_LV_FLEX_FLOW_COLUMN); + lvgl::lv_obj_set_flex_align( + container, + lvgl::lv_flex_align_t_LV_FLEX_ALIGN_START, + lvgl::lv_flex_align_t_LV_FLEX_ALIGN_START, + lvgl::lv_flex_align_t_LV_FLEX_ALIGN_CENTER, + ); + lvgl::lv_obj_set_style_pad_all(container, 10, lvgl::LV_STATE_DEFAULT); + + // Label: Configuration Mode + let mode_label = lvgl::lv_label_create(container); + let mode_text = translate!(c"Configuration Mode"); + lvgl::lv_label_set_text(mode_label, mode_text.as_ptr() as *const _); + + // Radio group for configuration modew + let radio_group = lvgl::lv_obj_create(container); + lvgl::lv_obj_set_size(radio_group, lvgl::LV_SIZE_CONTENT, lvgl::LV_SIZE_CONTENT); + lvgl::lv_obj_set_flex_flow(radio_group, lvgl::lv_flex_flow_t_LV_FLEX_FLOW_ROW); + + // Radio: None + let radio_none = lvgl::lv_radiobox_create(radio_group); + let none_text = translate!(c"None"); + lvgl::lv_checkbox_set_text(radio_none, none_text.as_ptr() as *const _); + + // Radio: DHCP + let radio_dhcp = lvgl::lv_radiobox_create(radio_group); + let dhcp_text = translate!(c"DHCP"); + lvgl::lv_checkbox_set_text(radio_dhcp, dhcp_text.as_ptr() as *const _); + + // Radio: Static + let radio_static = lvgl::lv_radiobox_create(radio_group); + let static_text = translate!(c"Static"); + lvgl::lv_checkbox_set_text(radio_static, static_text.as_ptr() as *const _); + + // Container for static configuration (hidden by default) + + // IP Address + CIDR input + let address_label = lvgl::lv_label_create(container); + let address_text = translate!(c"Address"); + lvgl::lv_label_set_text(address_label, address_text.as_ptr() as *const _); + + let address_input = lvgl::lv_textarea_create(container); + if is_ipv6 { + lvgl::lv_textarea_set_placeholder_text( + address_input, + c"2001:0db8:85a3::8a2e:0370:7334/64".as_ptr(), + ); + } else { + lvgl::lv_textarea_set_placeholder_text(address_input, c"192.168.1.100/24".as_ptr()); + } + lvgl::lv_textarea_set_one_line(address_input, true); + + // Gateway input + let gateway_label = lvgl::lv_label_create(container); + let gateway_text = translate!(c"Gateway"); + lvgl::lv_label_set_text(gateway_label, gateway_text.as_ptr() as *const _); + + let gateway_input = lvgl::lv_textarea_create(container); + if is_ipv6 { + lvgl::lv_textarea_set_placeholder_text(gateway_input, c"fe80::1".as_ptr()); + } else { + lvgl::lv_textarea_set_placeholder_text(gateway_input, c"192.168.1.1".as_ptr()); + } + lvgl::lv_textarea_set_one_line(gateway_input, true); + + // DNS inputs + let dns_label = lvgl::lv_label_create(container); + let dns_text = translate!(c"DNS Servers"); + lvgl::lv_label_set_text(dns_label, dns_text.as_ptr() as *const _); + + let mut dns_inputs = [null_mut(); 3]; + + for (i, dns_input) in dns_inputs.iter_mut().enumerate() { + *dns_input = lvgl::lv_textarea_create(container); + + if is_ipv6 { + lvgl::lv_textarea_set_placeholder_text( + *dns_input, + if i == 0 { + c"2606:4700:4700::1111".as_ptr() + } else if i == 1 { + c"2001:4860:4860::8888".as_ptr() + } else { + c"2001:4860:4860::8844".as_ptr() + }, + ); + } else { + lvgl::lv_textarea_set_placeholder_text( + *dns_input, + if i == 0 { + c"1.1.1.1".as_ptr() + } else if i == 1 { + c"8.8.8.8".as_ptr() + } else { + c"8.8.4.4".as_ptr() + }, + ); + } + + lvgl::lv_textarea_set_one_line(*dns_input, true); + } + + // TODO: Connect radio buttons to show/hide static_container + // TODO: Implement radio button group behavior (only one can be selected) + + Ok(IpConfigurationTab { + _container: container, + _radio_group: radio_group, + _radio_none: radio_none, + _radio_dhcp: radio_dhcp, + _radio_static: radio_static, + _address_input: address_input, + _gateway_input: gateway_input, + _dns_inputs: dns_inputs, + }) + } + } + + async fn get_dns_servers(file: &mut File) -> Result> { + Ok(FileControlIterator::new(file, GET_DNS_SERVER_COUNT, GET_DNS_SERVER).await?) + } + + async fn get_routes(file: &mut File) -> Result> { + Ok(FileControlIterator::new(file, GET_ROUTE_COUNT, GET_ROUTE).await?) + } + + async fn get_ip_addresses(file: &mut File) -> Result> { + Ok(FileControlIterator::new(file, GET_IP_ADDRESS_COUNT, GET_IP_ADDRESS).await?) + } + + async fn get_mac_address(file: &mut File) -> Result { + Ok(file.control(GET_HARDWARE_ADDRESS, &()).await?) + } + + pub async fn new(interface: String, parent_tabview: *mut lvgl::lv_obj_t) -> Result { + // Create a container for the entire configuration panel + let main_container = unsafe { lvgl::lv_obj_create(parent_tabview) }; + if main_container.is_null() { + return Err(Error::FailedToCreateObject); + } + + unsafe { + lvgl::lv_obj_add_flag(main_container, lvgl::lv_obj_flag_t_LV_OBJ_FLAG_FLOATING); + lvgl::lv_obj_set_size(main_container, lvgl::lv_pct(100), lvgl::lv_pct(100)); + lvgl::lv_obj_set_flex_flow(main_container, lvgl::lv_flex_flow_t_LV_FLEX_FLOW_COLUMN); + lvgl::lv_obj_set_style_pad_all(main_container, 0, lvgl::LV_STATE_DEFAULT); + lvgl::lv_obj_set_style_border_width(main_container, 0, lvgl::LV_STATE_DEFAULT); + } + + // Create button container + let button_container = unsafe { lvgl::lv_obj_create(main_container) }; + if button_container.is_null() { + return Err(Error::FailedToCreateObject); + } + + unsafe { + lvgl::lv_obj_set_size(button_container, lvgl::lv_pct(100), lvgl::LV_SIZE_CONTENT); + lvgl::lv_obj_set_style_border_side( + button_container, + lvgl::lv_border_side_t_LV_BORDER_SIDE_BOTTOM, + lvgl::LV_PART_MAIN, + ); + } + + let cancel_button = unsafe { + // Cancel button + let cancel_button = lvgl::lv_button_create(button_container); + let cancel_label = lvgl::lv_label_create(cancel_button); + let cancel_text = translate!(c"Cancel"); + lvgl::lv_label_set_text(cancel_label, cancel_text.as_ptr() as *const _); + lvgl::lv_obj_set_align(cancel_button, lvgl::lv_align_t_LV_ALIGN_LEFT_MID); + + cancel_button + }; + + // Title + unsafe { + let title_label = lvgl::lv_label_create(button_container); + let title_text = c"Interface Configuration"; + lvgl::lv_label_set_text(title_label, title_text.as_ptr() as *const _); + lvgl::lv_obj_set_align(title_label, lvgl::lv_align_t_LV_ALIGN_CENTER); + } + + // Apply button + let apply_button = unsafe { + let apply_button = lvgl::lv_button_create(button_container); + let apply_label = lvgl::lv_label_create(apply_button); + let apply_text = translate!(c"Apply"); + lvgl::lv_label_set_text(apply_label, apply_text.as_ptr() as *const _); + lvgl::lv_obj_set_align(apply_button, lvgl::lv_align_t_LV_ALIGN_RIGHT_MID); + apply_button + }; + + // Create a tabview for IPv4 and IPv6 configurations + let config_tabview = unsafe { lvgl::lv_tabview_create(main_container) }; + if config_tabview.is_null() { + return Err(Error::FailedToCreateObject); + } + + unsafe { + lvgl::lv_obj_set_width(config_tabview, lvgl::lv_pct(100)); + lvgl::lv_obj_set_flex_grow(config_tabview, 1); + } + + // Create general tab + let general_tab = unsafe { + let tab = lvgl::lv_tabview_add_tab( + config_tabview, + translate!(c"General").as_ptr() as *const _, + ); + if tab.is_null() { + return Err(Error::FailedToCreateObject); + } + tab + }; + + // Create general tab content + let virtual_file_system = virtual_file_system::get_instance(); + + let mut file = open_interface(virtual_file_system, &interface).await?; + + let _general_tab = Self::create_general_tab(general_tab, &mut file).await?; + + file.close(virtual_file_system).await?; + + // Create IPv4 tab + let _ipv4_tab = Self::create_ip_configuration_tab(config_tabview, false, b"IPv4\0")?; + + // Create IPv6 tab + let _ipv6_tab = Self::create_ip_configuration_tab(config_tabview, true, b"IPv6\0")?; + + let interface = Self { + main_container, + cancel_button, + apply_button, + interface, + }; + + Ok(interface) + } + + pub async fn handle_event(&mut self, event: &xila::graphics::Event) -> bool { + // Allow only selection of one radio button at a time + + if event.code == EventKind::Clicked { + if event.target == self.cancel_button { + log::information!("Cancel button clicked in interface panel"); + // Close the panel without applying changes + return false; + } else if event.target == self.apply_button { + log::information!("Apply button clicked in interface panel"); + // Apply the configuration changes + return false; + } + } + + true + } +} + +impl Drop for InterfacePanel { + fn drop(&mut self) { + unsafe { + lvgl::lv_obj_delete(self.main_container); + log::information!("Interface panel for {} has been deleted", self.interface); + } + } +} diff --git a/executables/settings/src/tabs/network/mod.rs b/executables/settings/src/tabs/network/mod.rs new file mode 100644 index 00000000..25b71eac --- /dev/null +++ b/executables/settings/src/tabs/network/mod.rs @@ -0,0 +1,242 @@ +mod interface; + +use crate::{Error, Result}; +use alloc::{ffi::CString, string::ToString}; +use core::{ffi::CStr, ptr::null_mut, time::Duration}; +use interface::*; +use xila::{ + file_system::{AccessFlags, Path}, + graphics::{Event, EventKind, lvgl, symbol}, + log, + network::{self, InterfaceKind}, + virtual_file_system::{self, Directory, File, VirtualFileSystem}, +}; + +pub struct NetworkTab { + tab_container: *mut lvgl::lv_obj_t, + interfaces_list: *mut lvgl::lv_obj_t, + last_update: Duration, + configuration_panel: Option, + parent_tabview: *mut lvgl::lv_obj_t, +} + +impl NetworkTab { + pub const UPDATE_INTERVAL: Duration = Duration::from_secs(30); + + pub fn new() -> Self { + Self { + tab_container: null_mut(), + interfaces_list: null_mut(), + last_update: Duration::from_secs(0), + configuration_panel: None, + parent_tabview: null_mut(), + } + } + + pub async fn get_interface_kind_status( + &self, + interface_name: &str, + ) -> Result<(InterfaceKind, bool)> { + let virtual_file_system = virtual_file_system::get_instance(); + + let mut file = open_interface(virtual_file_system, interface_name).await?; + + let kind = file.control(network::GET_KIND, &()).await?; + let is_up = file.control(network::IS_LINK_UP, &()).await?; + + file.close(virtual_file_system).await?; + + Ok((kind, is_up)) + } + pub async fn update_interfaces(&mut self) -> Result<()> { + // Clear existing list items + unsafe { + lvgl::lv_obj_clean(self.interfaces_list); + } + + let virtual_file_system = xila::virtual_file_system::get_instance(); + + let task_manager = xila::task::get_instance(); + + let task = task_manager.get_current_task_identifier().await; + + let mut directory = + Directory::open(virtual_file_system, task, Path::NETWORK_DEVICES).await?; + + while let Some(entry) = directory.read().await? { + if entry.name == "." || entry.name == ".." { + continue; + } + + let (kind, is_up) = self.get_interface_kind_status(&entry.name).await?; + + let label_text = + CString::new(entry.name.as_str()).map_err(|_| Error::FailedToCreateUiElement)?; + + let symbol = match kind { + InterfaceKind::Ethernet => symbol::NETWORK_WIRED, + InterfaceKind::WiFi => symbol::WIFI, + InterfaceKind::Unknown => c"?", + }; + + unsafe { + let button = lvgl::lv_list_add_button( + self.interfaces_list, + symbol.as_ptr() as _, + label_text.as_ptr() as *const _, + ); + + // center button content + lvgl::lv_obj_set_style_pad_all(button, 10, lvgl::LV_STATE_DEFAULT); + lvgl::lv_obj_set_flex_align( + button, + lvgl::lv_flex_align_t_LV_FLEX_ALIGN_CENTER, + lvgl::lv_flex_align_t_LV_FLEX_ALIGN_CENTER, + lvgl::lv_flex_align_t_LV_FLEX_ALIGN_SPACE_AROUND as _, + ); + + let switch = lvgl::lv_switch_create(button); + + log::information!( + "Interface {} is {:?}, link is {}", + entry.name, + kind, + if is_up { "up" } else { "down" } + ); + + lvgl::lv_obj_set_state(switch, lvgl::LV_STATE_CHECKED as _, is_up); + } + } + + Ok(()) + } + + pub async fn create_ui( + &mut self, + parent_tabview: *mut lvgl::lv_obj_t, + ) -> crate::error::Result<*mut lvgl::lv_obj_t> { + self.parent_tabview = parent_tabview; + + self.tab_container = + unsafe { lvgl::lv_tabview_add_tab(parent_tabview, c"Network".as_ptr() as *const _) }; + + if self.tab_container.is_null() { + return Err(crate::error::Error::FailedToCreateUiElement); + } + + // Create interface list + + unsafe { + self.interfaces_list = lvgl::lv_list_create(self.tab_container); + if self.interfaces_list.is_null() { + return Err(Error::FailedToCreateObject); + } + + // File list properties - use flex grow to fill remaining space + lvgl::lv_obj_set_width(self.interfaces_list, lvgl::lv_pct(100)); + lvgl::lv_obj_set_flex_grow(self.interfaces_list, 1); // Take remaining vertical space + + // Ensure proper scrolling behavior + lvgl::lv_obj_set_style_pad_all(self.interfaces_list, 0, lvgl::LV_STATE_DEFAULT); + } + + self.update_interfaces().await?; + + Ok(self.tab_container) + } + + pub async fn handle_event(&mut self, event: &Event) -> bool { + let time_manager = xila::time::get_instance(); + + let current_time = match time_manager.get_current_time() { + Ok(time) => time, + Err(_) => { + log::error!("Failed to get current time for network tab update"); + return false; + } + }; + + if current_time - self.last_update >= Self::UPDATE_INTERVAL { + if let Err(e) = self.update_interfaces().await { + log::error!("Failed to update network interfaces: {:?}", e); + } + + self.last_update = current_time; + } + + if let Some(panel) = &mut self.configuration_panel { + if !panel.handle_event(event).await { + self.configuration_panel.take(); + } + + return true; + } + + // check if any specific events need to be handled here + if event.code == EventKind::Clicked { + // Find which interface button was clicked + let parent = unsafe { lvgl::lv_obj_get_parent(event.target) }; + if parent == self.interfaces_list + && unsafe { lvgl::lv_obj_check_type(event.target, &lvgl::lv_list_button_class) } + { + let interface = unsafe { + let label = lvgl::lv_obj_get_child(event.target, 1); + + if label.is_null() { + log::error!("Failed to get label child from interface button"); + return false; + } + + let text = lvgl::lv_label_get_text(label); + + if text.is_null() { + log::error!("Failed to get text from label"); + return false; + } + + CStr::from_ptr(text as *const _) + .to_str() + .unwrap_or_default() + }; + + log::information!("Opening configuration panel for interface: {}", interface); + + match InterfacePanel::new(interface.to_string(), self.tab_container).await { + Ok(panel) => { + self.configuration_panel.replace(panel); + } + Err(e) => { + log::error!("Failed to create configuration panel: {:?}", e); + } + } + + return true; + } + } + + false + } +} + +pub async fn open_interface( + virtual_file_system: &VirtualFileSystem, + interface: &str, +) -> Result { + let task_manager = xila::task::get_instance(); + + let task = task_manager.get_current_task_identifier().await; + + let path = Path::NETWORK_DEVICES + .join(Path::from_str(interface)) + .ok_or(Error::FailedToCreateUiElement)?; + + let file = File::open( + virtual_file_system, + task, + &path, + AccessFlags::READ_WRITE.into(), + ) + .await?; + + Ok(file) +} diff --git a/executables/settings/src/tabs/password.rs b/executables/settings/src/tabs/password.rs index 0024a4a5..f402930b 100644 --- a/executables/settings/src/tabs/password.rs +++ b/executables/settings/src/tabs/password.rs @@ -186,13 +186,23 @@ impl PasswordTab { } impl PasswordTab { - pub fn create_ui( + pub async fn create_ui( &mut self, parent_tabview: *mut lvgl::lv_obj_t, ) -> Result<*mut lvgl::lv_obj_t> { let tab_container = unsafe { lvgl::lv_tabview_add_tab(parent_tabview, translate!(c"Password").as_ptr()) }; + unsafe { + lvgl::lv_obj_set_flex_flow(tab_container, lvgl::lv_flex_flow_t_LV_FLEX_FLOW_COLUMN); + lvgl::lv_obj_set_flex_align( + tab_container, + lvgl::lv_flex_align_t_LV_FLEX_ALIGN_CENTER, + lvgl::lv_flex_align_t_LV_FLEX_ALIGN_START, + lvgl::lv_flex_align_t_LV_FLEX_ALIGN_CENTER, + ); + } + if tab_container.is_null() { return Err(crate::error::Error::FailedToCreateUiElement); } @@ -213,45 +223,18 @@ impl PasswordTab { current_password_label, translate!(c"Current Password").as_ptr(), ); - lvgl::lv_obj_align( - current_password_label, - lvgl::lv_align_t_LV_ALIGN_TOP_LEFT, - 10, - 10, - ); let current_password_text_area = lvgl::lv_textarea_create(tab_container); lvgl::lv_textarea_set_password_mode(current_password_text_area, true); lvgl::lv_textarea_set_one_line(current_password_text_area, true); - lvgl::lv_obj_align_to( - current_password_text_area, - current_password_label, - lvgl::lv_align_t_LV_ALIGN_OUT_BOTTOM_LEFT, - 0, - 5, - ); // New password let new_password_label = lvgl::lv_label_create(tab_container); lvgl::lv_label_set_text(new_password_label, translate!(c"New Password").as_ptr()); - lvgl::lv_obj_align_to( - new_password_label, - current_password_text_area, - lvgl::lv_align_t_LV_ALIGN_OUT_BOTTOM_LEFT, - 0, - 20, - ); let new_password_text_area = lvgl::lv_textarea_create(tab_container); lvgl::lv_textarea_set_password_mode(new_password_text_area, true); lvgl::lv_textarea_set_one_line(new_password_text_area, true); - lvgl::lv_obj_align_to( - new_password_text_area, - new_password_label, - lvgl::lv_align_t_LV_ALIGN_OUT_BOTTOM_LEFT, - 0, - 5, - ); // Confirm password let confirm_password_label = lvgl::lv_label_create(tab_container); @@ -259,34 +242,13 @@ impl PasswordTab { confirm_password_label, translate!(c"Confirm Password").as_ptr(), ); - lvgl::lv_obj_align_to( - confirm_password_label, - new_password_text_area, - lvgl::lv_align_t_LV_ALIGN_OUT_BOTTOM_LEFT, - 0, - 20, - ); let confirm_password_text_area = lvgl::lv_textarea_create(tab_container); lvgl::lv_textarea_set_password_mode(confirm_password_text_area, true); lvgl::lv_textarea_set_one_line(confirm_password_text_area, true); - lvgl::lv_obj_align_to( - confirm_password_text_area, - confirm_password_label, - lvgl::lv_align_t_LV_ALIGN_OUT_BOTTOM_LEFT, - 0, - 5, - ); // Change password button let change_password_button = lvgl::lv_button_create(tab_container); - lvgl::lv_obj_align_to( - change_password_button, - confirm_password_text_area, - lvgl::lv_align_t_LV_ALIGN_OUT_BOTTOM_MID, - 0, - 30, - ); let button_label = lvgl::lv_label_create(change_password_button); lvgl::lv_label_set_text(button_label, translate!(c"Change Password").as_ptr()); diff --git a/executables/settings/tests/integration_test.rs b/executables/settings/tests/integration_test.rs index 3c21cf8d..6e53192b 100644 --- a/executables/settings/tests/integration_test.rs +++ b/executables/settings/tests/integration_test.rs @@ -12,7 +12,7 @@ async fn main() { use xila::executable::mount_executables; use xila::{executable, task, virtual_file_system}; - let standard = testing::initialize(true).await; + let standard = testing::initialize(true, true).await; mount_executables!( virtual_file_system::get_instance(), diff --git a/executables/shell/command_line/locales/en/messages.po b/executables/shell/command_line/locales/en/messages.po index 4f61e077..32c55935 100644 --- a/executables/shell/command_line/locales/en/messages.po +++ b/executables/shell/command_line/locales/en/messages.po @@ -108,6 +108,12 @@ msgstr "Failed to set current directory: {}" msgid "Failed to read directory entry: {}" msgstr "Failed to read directory entry: {}" +msgid "Failed to resolve domain: {}" +msgstr "Failed to resolve domain: {}" + +msgid "Failed to create socket: {}" +msgstr "Failed to create socket: {}" + msgid "Format error" msgstr "Format error" @@ -152,3 +158,27 @@ msgstr "Links" msgid "Size" msgstr "Size" + +msgid "{} record(s) for domain '{}':" +msgstr "{} record(s) for domain '{}':" + +msgid "No {} records found for domain '{}'" +msgstr "No {} records found for domain '{}'" + +msgid "Failed to resolve domain '{}' for {} records: {}" +msgstr "Failed to resolve domain '{}' for {} records: {}" + +msgid "Cannot resolve {}: Unknown host" +msgstr "Cannot resolve {}: Unknown host" + +msgid "PING {} ({}): {} data bytes" +msgstr "PING {} ({}): {} data bytes" + +msgid "{} bytes from {}: icmp_seq={} time={:.2} ms" +msgstr "{} bytes from {}: icmp_seq={} time={:.2} ms" + +msgid "Request timeout for icmp_seq {}" +msgstr "Request timeout for icmp_seq {}" + +msgid "Error: {}" +msgstr "Error: {}" \ No newline at end of file diff --git a/executables/shell/command_line/locales/fr/messages.po b/executables/shell/command_line/locales/fr/messages.po index 5408176b..53732da2 100644 --- a/executables/shell/command_line/locales/fr/messages.po +++ b/executables/shell/command_line/locales/fr/messages.po @@ -108,6 +108,12 @@ msgstr "Échec de la définition du répertoire courant: {}" msgid "Failed to read directory entry: {}" msgstr "Échec de la lecture de l'entrée du répertoire: {}" +msgid "Failed to resolve domain: {}" +msgstr "Échec de la résolution du domaine: {}" + +msgid "Failed to create socket: {}" +msgstr "Échec de la création du socket: {}" + msgid "Format error" msgstr "Erreur de formatage" @@ -152,3 +158,27 @@ msgstr "Liens" msgid "Size" msgstr "Taille" + +msgid "{} record(s) for domain '{}':" +msgstr "Enregistrement(s) {} pour le domaine '{}':" + +msgid "No {} records found for domain '{}'" +msgstr "Aucun enregistrement {} trouvé pour le domaine '{}'" + +msgid "Failed to resolve domain '{}' for {} records: {}" +msgstr "Échec de la résolution du domaine '{}' pour les enregistrements {}: {}" + +msgid "Cannot resolve {}: Unknown host" +msgstr "Impossible de résoudre {}: Hôte inconnu" + +msgid "PING {} ({}): {} data bytes" +msgstr "PING {} ({}): {} octets de données" + +msgid "{} bytes from {}: icmp_seq={} time={:.2} ms" +msgstr "{} octets de {}: icmp_seq={} temps={:.2} ms" + +msgid "Request timeout for icmp_seq {}" +msgstr "Délai d'attente dépassé pour icmp_seq {}" + +msgid "Error: {}" +msgstr "Erreur: {}" diff --git a/executables/shell/command_line/locales/messages.pot b/executables/shell/command_line/locales/messages.pot index 55a9c867..ae198a69 100644 --- a/executables/shell/command_line/locales/messages.pot +++ b/executables/shell/command_line/locales/messages.pot @@ -108,6 +108,12 @@ msgstr "" msgid "Failed to read directory entry: {}" msgstr "" +msgid "Failed to resolve domain: {}" +msgstr "" + +msgid "Failed to create socket: {}" +msgstr "" + msgid "Format error" msgstr "" @@ -152,3 +158,27 @@ msgstr "" msgid "Size" msgstr "" + +msgid "{} record(s) for domain '{}':" +msgstr "" + +msgid "No {} records found for domain '{}'" +msgstr "" + +msgid "Failed to resolve domain '{}' for {} records: {}" +msgstr "" + +msgid "Cannot resolve {}: Unknown host" +msgstr "" + +msgid "PING {} ({}): {} data bytes" +msgstr "" + +msgid "{} bytes from {}: icmp_seq={} time={:.2} ms" +msgstr "" + +msgid "Request timeout for icmp_seq {}" +msgstr "" + +msgid "Error: {}" +msgstr "" \ No newline at end of file diff --git a/executables/shell/command_line/src/commands/dns.rs b/executables/shell/command_line/src/commands/dns.rs new file mode 100644 index 00000000..539a4211 --- /dev/null +++ b/executables/shell/command_line/src/commands/dns.rs @@ -0,0 +1,132 @@ +use crate::{Error, Result, Shell, commands::check_no_more_arguments}; +use core::fmt::Write; +use getargs::{Arg, Options}; +use xila::{ + internationalization::translate, + network::{self, DnsQueryKind, DnsSocket}, +}; + +impl Shell { + fn format_kind(kind: DnsQueryKind) -> &'static str { + match kind { + DnsQueryKind::A => "A", + DnsQueryKind::Aaaa => "AAAA", + DnsQueryKind::Cname => "CNAME", + DnsQueryKind::Ns => "NS", + DnsQueryKind::Soa => "SOA", + _ => "UNKNOWN", + } + } + + async fn resolve( + &mut self, + socket: &DnsSocket, + domain: &str, + kind: DnsQueryKind, + ) -> Result<()> { + match socket.resolve(domain, kind).await { + Ok(ip) => { + writeln!( + self.standard.out(), + translate!("{} record(s) for domain '{}':"), + Self::format_kind(kind), + domain + )?; + for address in &ip { + writeln!(self.standard.out(), " - {}", address)?; + } + } + Err(network::Error::Failed) => { + writeln!( + self.standard.out(), + translate!("No {} records found for domain '{}'"), + Self::format_kind(kind), + domain + )?; + } + Err(e) => { + write!( + self.standard.out(), + translate!("Failed to resolve domain '{}' for {} records: {}"), + domain, + Self::format_kind(kind), + e + )?; + } + } + Ok(()) + } + + pub async fn dns_resolve<'a, I>(&mut self, options: &mut Options<&'a str, I>) -> Result<()> + where + I: Iterator, + { + let mut domain = ""; + let mut a_enabled = false; + let mut aaaa_enabled = false; + let mut cname_enabled = false; + let mut ns_enabled = false; + let mut soa_enabled = false; + let mut default = true; + + while let Some(argument) = options.next_arg()? { + if let Arg::Long(_) | Arg::Short(_) = &argument { + default = false; + } + + match argument { + Arg::Short('a') | Arg::Long("a") => { + a_enabled = true; + } + Arg::Short('A') | Arg::Long("aaaa") => { + aaaa_enabled = true; + } + Arg::Short('c') | Arg::Long("cname") => { + cname_enabled = true; + } + Arg::Short('n') | Arg::Long("ns") => { + ns_enabled = true; + } + Arg::Short('s') | Arg::Long("soa") => { + soa_enabled = true; + } + Arg::Positional(p) => { + if !domain.is_empty() { + return Err(crate::Error::InvalidNumberOfArguments); + } + domain = p; + } + _ => { + return Err(crate::Error::InvalidOption); + } + } + } + + check_no_more_arguments(options)?; + + let socket = network::get_instance() + .new_dns_socket(None) + .await + .map_err(Error::FailedToCreateSocket)?; + + if a_enabled || default { + self.resolve(&socket, domain, DnsQueryKind::A).await?; + } + if aaaa_enabled || default { + self.resolve(&socket, domain, DnsQueryKind::Aaaa).await?; + } + if cname_enabled { + self.resolve(&socket, domain, DnsQueryKind::Cname).await?; + } + if ns_enabled { + self.resolve(&socket, domain, DnsQueryKind::Ns).await?; + } + if soa_enabled { + self.resolve(&socket, domain, DnsQueryKind::Soa).await?; + } + + socket.close().await.map_err(Error::FailedToCreateSocket)?; + + Ok(()) + } +} diff --git a/executables/shell/command_line/src/commands/ip.rs b/executables/shell/command_line/src/commands/ip.rs new file mode 100644 index 00000000..f1226928 --- /dev/null +++ b/executables/shell/command_line/src/commands/ip.rs @@ -0,0 +1,193 @@ +use crate::{ + Shell, + error::{Error, Result}, +}; +use core::fmt::Write; +use xila::{ + file_system::{AccessFlags, Path}, + log, + network::{GET_IP_ADDRESS, GET_IP_ADDRESS_COUNT, GET_ROUTE, GET_ROUTE_COUNT, GET_STATE}, + task::{self, TaskIdentifier}, + virtual_file_system::{self, Directory, File, FileControlIterator, VirtualFileSystem}, +}; + +impl Shell { + pub async fn open_interface( + virtual_file_system: &VirtualFileSystem, + interface: &str, + ) -> Result { + let task_manager = xila::task::get_instance(); + + let task = task_manager.get_current_task_identifier().await; + + let path = Path::NETWORK_DEVICES + .join(interface) + .ok_or(Error::FailedToJoinPath)?; + + File::open(virtual_file_system, task, &path, AccessFlags::Read.into()) + .await + .map_err(Error::FailedToOpenFile) + } + + async fn show_routes_interface( + &mut self, + interface: &str, + file: &mut File, + ) -> crate::Result<()> { + let mut routes = FileControlIterator::new(file, GET_ROUTE_COUNT, GET_ROUTE) + .await + .map_err(Error::FailedToOpenFile)?; + + while let Some(route) = routes.next().await.map_err(Error::FailedToOpenFile)? { + writeln!( + self.standard.out(), + "{} via {} device {}", + route.cidr, + route.via_router, + interface + )?; + } + + Ok(()) + } + + async fn show_routes( + &mut self, + virtual_file_system: &VirtualFileSystem, + task: TaskIdentifier, + ) -> crate::Result<()> { + let mut directory = Directory::open(virtual_file_system, task, Path::NETWORK_DEVICES) + .await + .map_err(Error::FailedToOpenDirectory)?; + + while let Some(entry) = directory + .read() + .await + .map_err(Error::FailedToReadDirectoryEntry)? + { + if entry.name == "." || entry.name == ".." { + continue; + } + + let mut file = Self::open_interface(virtual_file_system, &entry.name).await?; + + self.show_routes_interface(&entry.name, &mut file).await?; + } + + Ok(()) + } + + async fn show_address_interface(&mut self, file: &mut File) -> crate::Result<()> { + let mut addresses = FileControlIterator::new(file, GET_IP_ADDRESS_COUNT, GET_IP_ADDRESS) + .await + .map_err(Error::FailedToOpenFile)?; + + while let Some(address) = addresses.next().await.map_err(Error::FailedToOpenFile)? { + writeln!(self.standard.out(), " {} ", address)?; + } + + Ok(()) + } + + async fn show_address( + &mut self, + virtual_file_system: &VirtualFileSystem, + task: TaskIdentifier, + ) -> crate::Result<()> { + let mut directory = Directory::open(virtual_file_system, task, Path::NETWORK_DEVICES) + .await + .map_err(Error::FailedToOpenDirectory)?; + + let mut index = 1; + + while let Some(entry) = directory + .read() + .await + .map_err(Error::FailedToReadDirectoryEntry)? + { + if entry.name == "." || entry.name == ".." { + continue; + } + + log::information!("Showing address for interface {}", entry.name); + + let mut file = Self::open_interface(virtual_file_system, &entry.name).await?; + + let state = file + .control(GET_STATE, &()) + .await + .map_err(Error::FailedToOpenFile)?; + + let state = if state { "Enabled" } else { "Disabled" }; + + let status = file + .control(xila::network::IS_LINK_UP, &()) + .await + .map_err(Error::FailedToOpenFile)?; + + let status: &str = if status { "Up" } else { "Down" }; + + let hardware_address = file + .control(xila::network::GET_HARDWARE_ADDRESS, &()) + .await + .map_err(Error::FailedToOpenFile)?; + + let maximum_transmission_unit = file + .control(xila::network::GET_MAXIMUM_TRANSMISSION_UNIT, &()) + .await + .map_err(Error::FailedToOpenFile)?; + + writeln!( + self.standard.out(), + "{}. {} [{}, {}, MTU: {}]", + index, + entry.name, + state, + status, + maximum_transmission_unit + )?; + writeln!( + self.standard.out(), + " Hardware Address: {:x}:{:x}:{:x}:{:x}:{:x}:{:x}", + hardware_address[0], + hardware_address[1], + hardware_address[2], + hardware_address[3], + hardware_address[4], + hardware_address[5], + )?; + + self.show_address_interface(&mut file).await?; + + index += 1; + } + + Ok(()) + } + + pub async fn ip<'a, I>( + &mut self, + options: &mut getargs::Options<&'a str, I>, + ) -> crate::Result<()> + where + I: Iterator, + { + let command = options + .next_positional() + .ok_or(crate::Error::MissingPositionalArgument("command"))?; + + let virtual_file_system = virtual_file_system::get_instance(); + let task_manager = task::get_instance(); + let task = task_manager.get_current_task_identifier().await; + + match command { + "address" | "a" => self.show_address(virtual_file_system, task).await?, + "route" | "r" => self.show_routes(virtual_file_system, task).await?, + _ => { + return Err(crate::Error::InvalidOption); + } + } + + Ok(()) + } +} diff --git a/executables/shell/command_line/src/commands/list.rs b/executables/shell/command_line/src/commands/list.rs index b7270145..f7ab4a6d 100644 --- a/executables/shell/command_line/src/commands/list.rs +++ b/executables/shell/command_line/src/commands/list.rs @@ -43,9 +43,7 @@ impl Shell { .map_err(Error::FailedToReadDirectoryEntry)? { if long { - let entry_path = path - .join(Path::from_str(&entry.name)) - .ok_or(Error::FailedToJoinPath)?; + let entry_path = entry.join_path(path).ok_or(Error::FailedToJoinPath)?; let statistics = virtual_file_system .get_statistics(&entry_path) diff --git a/executables/shell/command_line/src/commands/mod.rs b/executables/shell/command_line/src/commands/mod.rs index 00a49075..31a096c5 100644 --- a/executables/shell/command_line/src/commands/mod.rs +++ b/executables/shell/command_line/src/commands/mod.rs @@ -3,11 +3,14 @@ mod change_directory; mod clear; mod concatenate; mod directory; +mod dns; mod echo; mod environment_variables; mod execute; mod exit; +mod ip; mod list; +mod ping; mod statistics; mod web_request; diff --git a/executables/shell/command_line/src/commands/ping.rs b/executables/shell/command_line/src/commands/ping.rs new file mode 100644 index 00000000..ec850503 --- /dev/null +++ b/executables/shell/command_line/src/commands/ping.rs @@ -0,0 +1,141 @@ +use core::fmt::Write; +use getargs::Arg; +use xila::{ + internationalization::translate, + network::{self, DnsQueryKind, Duration, IcmpEndpoint}, +}; + +use crate::{Error, Shell}; + +const ICMP_IDENTIFIER: u16 = 0x22b; + +impl Shell { + pub async fn ping<'a, I>( + &mut self, + options: &mut getargs::Options<&'a str, I>, + ) -> crate::Result<()> + where + I: Iterator, + { + let mut count = 4; + let mut timeout_seconds = 5; + let mut target: &'a str = ""; + let mut payload_size = 56; + + while let Some(argument) = options.next_arg()? { + match argument { + Arg::Short('c') | Arg::Long("count") => { + let value = options + .next_positional() + .ok_or(crate::Error::MissingPositionalArgument("count"))?; + count = value.parse().map_err(|_| crate::Error::InvalidOption)?; + } + Arg::Short('t') | Arg::Long("timeout") => { + let value = options + .next_positional() + .ok_or(crate::Error::MissingPositionalArgument("timeout"))?; + timeout_seconds = value.parse().map_err(|_| crate::Error::InvalidOption)?; + } + Arg::Short('s') | Arg::Long("size") => { + let value = options + .next_positional() + .ok_or(crate::Error::MissingPositionalArgument("size"))?; + payload_size = value.parse().map_err(|_| crate::Error::InvalidOption)?; + } + Arg::Positional(p) => { + if !target.is_empty() { + return Err(crate::Error::InvalidNumberOfArguments); + } + target = p; + } + _ => { + return Err(crate::Error::InvalidOption); + } + } + } + + let network = network::get_instance(); + + let dns_socket = network + .new_dns_socket(None) + .await + .map_err(Error::FailedToCreateSocket)?; + + let resolved_target = dns_socket + .resolve(target, DnsQueryKind::A | DnsQueryKind::Aaaa) + .await + .map(|s| s.first().cloned()) + .map_err(Error::FailedToResolve)?; + + dns_socket + .close() + .await + .map_err(Error::FailedToCreateSocket)?; + + let resolved_target = match resolved_target { + Some(ip) => ip, + None => { + writeln!( + self.standard.out(), + translate!("Cannot resolve {}: Unknown host"), + target + )?; + return Ok(()); + } + }; + + writeln!( + self.standard.out(), + translate!("PING {} ({}): {} data bytes"), + target, + &resolved_target, + 56 + )?; + + let socket = network + .new_icmp_socket(256, 256, 1, 1, None) + .await + .map_err(Error::FailedToCreateSocket)?; + + socket + .bind(IcmpEndpoint::Identifier(ICMP_IDENTIFIER)) + .await + .map_err(Error::FailedToCreateSocket)?; + + for i in 0..count { + match socket + .ping( + &resolved_target, + i, + ICMP_IDENTIFIER, + Duration::from_seconds(timeout_seconds), + payload_size, + ) + .await + { + Ok(duration) => { + writeln!( + self.standard.out(), + translate!("{} bytes from {}: icmp_seq={} time={:.2} ms"), + payload_size, + resolved_target, + i, + duration.as_milliseconds() + )?; + } + Err(network::Error::TimedOut) => { + writeln!( + self.standard.out(), + translate!("Request timeout for icmp_seq {}"), + i + )?; + } + Err(e) => { + writeln!(self.standard.out(), translate!("Error: {}"), e)?; + } + } + } + + Ok(()) + } +} diff --git a/executables/shell/command_line/src/error.rs b/executables/shell/command_line/src/error.rs index da29bd65..bd67a920 100644 --- a/executables/shell/command_line/src/error.rs +++ b/executables/shell/command_line/src/error.rs @@ -2,8 +2,8 @@ use core::fmt::Display; use core::num::{NonZeroU16, NonZeroUsize}; use alloc::fmt; -use xila::virtual_file_system; use xila::{authentication, internationalization::translate, task}; +use xila::{network, virtual_file_system}; pub type Result = core::result::Result; @@ -39,6 +39,8 @@ pub enum Error { InvalidOption, FailedToGetMetadata(virtual_file_system::Error), FailedToReadDirectoryEntry(virtual_file_system::Error), + FailedToResolve(network::Error), + FailedToCreateSocket(network::Error), Format, } @@ -176,6 +178,12 @@ impl Display for Error { error ) } + Error::FailedToResolve(error) => { + write!(formatter, translate!("Failed to resolve domain: {}"), error) + } + Error::FailedToCreateSocket(error) => { + write!(formatter, translate!("Failed to create socket: {}"), error) + } Error::Format => { write!(formatter, translate!("Format error")) } diff --git a/executables/shell/command_line/src/lib.rs b/executables/shell/command_line/src/lib.rs index 3bfba01a..4d01b1bf 100644 --- a/executables/shell/command_line/src/lib.rs +++ b/executables/shell/command_line/src/lib.rs @@ -66,8 +66,7 @@ impl Shell { where I: IntoIterator + Clone, { - let mut options: getargs::Options<&'a str, ::IntoIter> = - getargs::Options::new(input.clone().into_iter()); + let mut options = getargs::Options::new(input.clone().into_iter()); let next_positional = match options.next_positional() { Some(arg) => arg, @@ -87,6 +86,9 @@ impl Shell { "unset" => self.remove_environment_variable(&mut options).await, "rm" => self.remove(&mut options).await, "web_request" => self.web_request(&mut options).await, + "dns_resolve" => self.dns_resolve(&mut options).await, + "ping" => self.ping(&mut options).await, + "ip" => self.ip(&mut options).await, _ => self.execute(input, paths).await, }; diff --git a/executables/shell/command_line/tests/integration_test.rs b/executables/shell/command_line/tests/integration_test.rs index 1e1dbc6c..f75c3af8 100644 --- a/executables/shell/command_line/tests/integration_test.rs +++ b/executables/shell/command_line/tests/integration_test.rs @@ -11,7 +11,7 @@ async fn main() { task, virtual_file_system, }; - let standard = testing::initialize(false).await; + let standard = testing::initialize(false, true).await; let virtual_file_system = virtual_file_system::get_instance(); let task = task::get_instance().get_current_task_identifier().await; diff --git a/executables/shell/graphical/Cargo.toml b/executables/shell/graphical/Cargo.toml index a64d9a13..7ec2fd98 100644 --- a/executables/shell/graphical/Cargo.toml +++ b/executables/shell/graphical/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] xila = { path = "../../../", features = ["host"] } miniserde = { workspace = true } +getargs = { version = "0.5" } [target.'cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))'.dev-dependencies] drivers_native = { workspace = true } diff --git a/executables/shell/graphical/locales/en/messages.po b/executables/shell/graphical/locales/en/messages.po index e3065174..112732ec 100644 --- a/executables/shell/graphical/locales/en/messages.po +++ b/executables/shell/graphical/locales/en/messages.po @@ -70,6 +70,9 @@ msgstr "Missing arguments" msgid "Failed to add shortcut: {}" msgstr "Failed to add shortcut: {}" +msgid "Failed to open directory: {}" +msgstr "Failed to open directory: {}" + #: login.user_name msgid "User name" msgstr "User name" diff --git a/executables/shell/graphical/locales/fr/messages.po b/executables/shell/graphical/locales/fr/messages.po index f5a08c45..df66d366 100644 --- a/executables/shell/graphical/locales/fr/messages.po +++ b/executables/shell/graphical/locales/fr/messages.po @@ -62,6 +62,9 @@ msgstr "Arguments manquants" msgid "Failed to add shortcut: {}" msgstr "Échec de l'ajout du raccourci: {}" +msgid "Failed to open directory: {}" +msgstr "Échec de l'ouverture du répertoire: {}" + #: login.user_name msgid "User name" msgstr "Nom d'utilisateur" diff --git a/executables/shell/graphical/locales/messages.pot b/executables/shell/graphical/locales/messages.pot index ed09bfb6..2b6e921c 100644 --- a/executables/shell/graphical/locales/messages.pot +++ b/executables/shell/graphical/locales/messages.pot @@ -70,6 +70,9 @@ msgstr "" msgid "Failed to add shortcut: {}" msgstr "" +msgid "Failed to open directory: {}" +msgstr "" + #: login.user_name msgid "User name" msgstr "" diff --git a/executables/shell/graphical/src/error.rs b/executables/shell/graphical/src/error.rs index b54798b3..cc97e6b1 100644 --- a/executables/shell/graphical/src/error.rs +++ b/executables/shell/graphical/src/error.rs @@ -30,6 +30,7 @@ pub enum Error { NullCharacterInString(alloc::ffi::NulError), MissingArguments, FailedToAddShortcut(virtual_file_system::Error), + FailedToOpenDirectory(virtual_file_system::Error), } impl Error { @@ -133,6 +134,9 @@ impl Display for Error { Self::FailedToAddShortcut(error) => { write!(formatter, translate!("Failed to add shortcut: {}"), error) } + Self::FailedToOpenDirectory(error) => { + write!(formatter, translate!("Failed to open directory: {}"), error) + } } } } diff --git a/executables/shell/graphical/src/layout.rs b/executables/shell/graphical/src/layout.rs index 67bc03be..b41905a9 100644 --- a/executables/shell/graphical/src/layout.rs +++ b/executables/shell/graphical/src/layout.rs @@ -1,9 +1,15 @@ use crate::error::{Error, Result}; use alloc::{format, string::String}; +use core::ffi::CStr; use core::ptr::null_mut; -use xila::graphics::{self, EventKind, lvgl, symbols, theme}; +use core::time::Duration; +use xila::file_system::{AccessFlags, Path}; +use xila::graphics::{self, EventKind, lvgl, symbol, theme}; +use xila::log; +use xila::network::InterfaceKind; use xila::shared::unix_to_human_time; -use xila::time; +use xila::virtual_file_system::{Directory, File}; +use xila::{network, time, virtual_file_system}; const KEYBOARD_SIZE_RATIO: f64 = 3.0 / 1.0; @@ -14,7 +20,8 @@ pub struct Layout { clock: *mut lvgl::lv_obj_t, clock_string: String, _battery: *mut lvgl::lv_obj_t, - _wi_fi: *mut lvgl::lv_obj_t, + network: *mut lvgl::lv_obj_t, + last_update: Duration, } impl Drop for Layout { @@ -104,28 +111,121 @@ pub unsafe extern "C" fn screen_event_handler(event: *mut lvgl::lv_event_t) { } impl Layout { - pub async fn r#loop(&mut self) { - self.update_clock().await; + pub const UPDATE_INTERVAL: Duration = Duration::from_secs(30); + + pub async fn run(&mut self) { + let current_time = match time::get_instance().get_current_time() { + Ok(time) => time, + Err(e) => { + log::error!("Failed to get current time: {}", e); + return; + } + }; + + if current_time - self.last_update < Self::UPDATE_INTERVAL { + return; + } + + self.update_clock(current_time).await; + + if let Err(e) = self.update_network_icon().await { + log::error!("Failed to update network icon: {}", e); + } + + self.last_update = current_time; } - async fn update_clock(&mut self) { - // - Update the clock - let current_time = time::get_instance().get_current_time(); + async fn get_interface_symbol(&self, file: &mut File) -> Result> { + let is_up = file + .control(network::IS_LINK_UP, &()) + .await + .map_err(Error::FailedToOpenDirectory)?; - if let Ok(current_time) = current_time { - let (_, _, _, hour, minute, _) = unix_to_human_time(current_time.as_secs() as i64); + if !is_up { + return Ok(None); + } - graphics::lock!({ - self.clock_string = format!("{hour:02}:{minute:02}\0"); + let kind = file + .control(network::GET_KIND, &()) + .await + .map_err(Error::FailedToOpenDirectory)?; - unsafe { - lvgl::lv_label_set_text_static( - self.clock, - self.clock_string.as_ptr() as *const i8, - ); + let symbol = match kind { + InterfaceKind::WiFi => symbol::WIFI, + InterfaceKind::Ethernet => symbol::NETWORK_WIRED, + InterfaceKind::Unknown => c"?", + }; + + Ok(Some(symbol)) + } + + async fn get_network_symbol(&self) -> Result<&CStr> { + // Browse the network interfaces in the /devices/network directory + + let virtual_file_system = virtual_file_system::get_instance(); + + let task_manager = xila::task::get_instance(); + + let task = task_manager.get_current_task_identifier().await; + + let mut directory = Directory::open(virtual_file_system, task, Path::NETWORK_DEVICES) + .await + .map_err(Error::FailedToOpenDirectory)?; + + while let Some(entry) = directory + .read() + .await + .map_err(Error::FailedToOpenDirectory)? + { + if entry.name == "." || entry.name == ".." { + continue; + } + + let entry_path = entry.join_path(Path::NETWORK_DEVICES); + + if let Some(entry_path) = entry_path { + let mut file = File::open( + virtual_file_system, + task, + &entry_path, + AccessFlags::Read.into(), + ) + .await + .map_err(Error::FailedToOpenDirectory)?; + + let symbol = self.get_interface_symbol(&mut file).await?; + + if let Some(symbol) = symbol { + return Ok(symbol); } - }); + } } + + Ok(c"") + } + + async fn update_network_icon(&mut self) -> Result<()> { + let symbol = self.get_network_symbol().await?; + + graphics::lock!({ + unsafe { + lvgl::lv_label_set_text_static(self.network, symbol.as_ptr()); + } + }); + + Ok(()) + } + + async fn update_clock(&mut self, current_time: Duration) { + let (_, _, _, hour, minute, _) = unix_to_human_time(current_time.as_secs() as i64); + + graphics::lock!({ + self.clock_string = format!("{hour:02}:{minute:02}\0"); + + unsafe { + lvgl::lv_label_set_text_static(self.clock, self.clock_string.as_ptr() as *const i8); + } + }); } pub fn get_windows_parent(&self) -> *mut lvgl::lv_obj_t { @@ -207,6 +307,11 @@ impl Layout { lvgl::lv_obj_set_style_pad_all(tray, 0, lvgl::LV_STATE_DEFAULT); lvgl::lv_obj_set_style_border_width(tray, 0, lvgl::LV_STATE_DEFAULT); lvgl::lv_obj_align(tray, lvgl::lv_align_t_LV_ALIGN_RIGHT_MID, 0, 0); + lvgl::lv_obj_set_style_bg_opa( + tray, + lvgl::LV_OPA_TRANSP as _, + lvgl::LV_STATE_DEFAULT, + ); tray } @@ -214,18 +319,18 @@ impl Layout { // - - Create a label for the WiFi - let wi_fi = unsafe { + let network = unsafe { // - - Create a label for the WiFi - let wi_fi = lvgl::lv_label_create(tray); + let network = lvgl::lv_label_create(tray); - if wi_fi.is_null() { + if network.is_null() { return Err(Error::FailedToCreateObject); } - lvgl::lv_label_set_text(wi_fi, symbols::WIFI.as_ptr()); + lvgl::lv_label_set_text(network, c"".as_ptr()); - wi_fi + network }; // - - Create a label for the battery @@ -237,7 +342,7 @@ impl Layout { return Err(Error::FailedToCreateObject); } - lvgl::lv_label_set_text(battery, symbols::BATTERY_3.as_ptr()); + lvgl::lv_label_set_text_static(battery, symbol::BATTERY_3.as_ptr()); battery }; @@ -304,7 +409,8 @@ impl Layout { clock, clock_string: String::with_capacity(6), _battery: battery, - _wi_fi: wi_fi, + network, + last_update: Duration::ZERO, } }); diff --git a/executables/shell/graphical/src/lib.rs b/executables/shell/graphical/src/lib.rs index 88c6ba33..2b614008 100644 --- a/executables/shell/graphical/src/lib.rs +++ b/executables/shell/graphical/src/lib.rs @@ -12,21 +12,35 @@ extern crate alloc; use crate::{desk::Desk, error::Error}; use alloc::{boxed::Box, string::String, vec::Vec}; +use core::fmt::Write; use core::num::NonZeroUsize; use core::time::Duration; +use getargs::Arg; use home::Home; use layout::Layout; use login::Login; -use xila::executable::{self, ArgumentsParser, ExecutableTrait, Standard}; +use xila::executable::{self, ExecutableTrait, Standard}; use xila::task; use xila::users; -pub async fn main(standard: Standard, arguments: Vec) -> Result<(), NonZeroUsize> { - let mut parsed_arguments = ArgumentsParser::new(&arguments); +pub async fn main(mut standard: Standard, arguments: Vec) -> Result<(), NonZeroUsize> { + let arguments = arguments.iter().map(|s| s.as_str()); - let show_keyboard = parsed_arguments - .find_map(|argument| Some(argument.options.get_option("show-keyboard").is_some())) - .unwrap_or(false); + let mut options = getargs::Options::new(arguments); + + let mut show_keyboard = false; + + while let Some(argument) = options.next_arg().map_err(|e| { + writeln!(standard.error(), "{}", e).ok(); + NonZeroUsize::new(1).unwrap() + })? { + match argument { + Arg::Short('k') | Arg::Long("show-keyboard") => { + show_keyboard = true; + } + _ => {} + } + } Shell::new(standard, show_keyboard).await.main().await } @@ -66,7 +80,7 @@ impl Shell { pub async fn main(&mut self) -> Result<(), NonZeroUsize> { while self.running { - self.layout.r#loop().await; + self.layout.run().await; if let Some(login) = &mut self.login { login.event_handler().await; diff --git a/executables/shell/graphical/tests/integration_test.rs b/executables/shell/graphical/tests/integration_test.rs index 636921f5..1b37e9de 100644 --- a/executables/shell/graphical/tests/integration_test.rs +++ b/executables/shell/graphical/tests/integration_test.rs @@ -13,7 +13,7 @@ async fn main() { use xila::virtual_file_system::File; use xila::{executable, task, virtual_file_system}; - let standard = testing::initialize(true).await; + let standard = testing::initialize(true, true).await; let task_manager = task::get_instance(); let virtual_file_system = virtual_file_system::get_instance(); diff --git a/executables/terminal/src/executable.rs b/executables/terminal/src/executable.rs index 1e98c888..9e4524d5 100644 --- a/executables/terminal/src/executable.rs +++ b/executables/terminal/src/executable.rs @@ -8,8 +8,8 @@ use xila::virtual_file_system::{File, VirtualFileSystem}; pub struct TerminalExecutable; impl TerminalExecutable { - pub async fn new<'a>( - virtual_file_system: &'a VirtualFileSystem<'a>, + pub async fn new( + virtual_file_system: &VirtualFileSystem, task: TaskIdentifier, ) -> Result { let _ = virtual_file_system diff --git a/executables/terminal/tests/integration_test.rs b/executables/terminal/tests/integration_test.rs index d121904d..cc8bfb3e 100644 --- a/executables/terminal/tests/integration_test.rs +++ b/executables/terminal/tests/integration_test.rs @@ -13,7 +13,7 @@ async fn main() { use xila::executable::mount_executables; use xila::{executable, task, virtual_file_system}; - let standard = testing::initialize(true).await; + let standard = testing::initialize(true, false).await; let virtual_file_system = virtual_file_system::get_instance(); let task_instance = task::get_instance(); diff --git a/executables/wasm/tests/integration_test.rs b/executables/wasm/tests/integration_test.rs index 29ba4e0b..61e5423d 100644 --- a/executables/wasm/tests/integration_test.rs +++ b/executables/wasm/tests/integration_test.rs @@ -15,7 +15,7 @@ async fn main() { use xila::virtual_file_system; use xila::virtual_machine; - let standard = testing::initialize(false).await; + let standard = testing::initialize(false, false).await; let virtual_file_system = virtual_file_system::get_instance(); let task_instance = task::get_instance(); diff --git a/modules/abi/definitions/src/file_system/directory.rs b/modules/abi/definitions/src/file_system/directory.rs index 6b54d463..84393d52 100644 --- a/modules/abi/definitions/src/file_system/directory.rs +++ b/modules/abi/definitions/src/file_system/directory.rs @@ -208,7 +208,7 @@ mod tests { use task::{TaskIdentifier, test}; use virtual_file_system::{Directory, File, VirtualFileSystem}; - async fn initialize() -> (TaskIdentifier, &'static VirtualFileSystem<'static>) { + async fn initialize() -> (TaskIdentifier, &'static VirtualFileSystem) { if !log::is_initialized() { log::initialize(&drivers_std::log::Logger).unwrap(); } @@ -228,14 +228,9 @@ mod tests { little_fs::FileSystem::format(device, cache_size).unwrap(); let file_system = little_fs::FileSystem::new(device, cache_size).unwrap(); - let virtual_file_system = virtual_file_system::initialize( - task_manager, - users_manager, - time_manager, - file_system, - None, - ) - .unwrap(); + let virtual_file_system = + virtual_file_system::initialize(task_manager, users_manager, time_manager, file_system) + .unwrap(); (task, virtual_file_system) } diff --git a/modules/authentication/src/group.rs b/modules/authentication/src/group.rs index 4dbfe464..bad2a5b9 100644 --- a/modules/authentication/src/group.rs +++ b/modules/authentication/src/group.rs @@ -131,8 +131,8 @@ pub fn get_group_file_path(group_name: &str) -> Result { /// - Path construction failures /// - File system errors (opening, reading) /// - JSON parsing errors -pub async fn read_group_file<'a>( - virtual_file_system: &'a VirtualFileSystem<'a>, +pub async fn read_group_file( + virtual_file_system: &VirtualFileSystem, buffer: &mut Vec, file: &str, ) -> Result { @@ -194,8 +194,8 @@ pub async fn read_group_file<'a>( /// - Group identifier generation or assignment failures /// - File system operations (directory creation, file writing) /// - Users manager operations (adding group) -pub async fn create_group<'a>( - virtual_file_system: &'a VirtualFileSystem<'a>, +pub async fn create_group( + virtual_file_system: &VirtualFileSystem, group_name: &str, group_identifier: Option, ) -> Result { diff --git a/modules/authentication/src/hash.rs b/modules/authentication/src/hash.rs index 77477e8e..3e0a1705 100644 --- a/modules/authentication/src/hash.rs +++ b/modules/authentication/src/hash.rs @@ -44,7 +44,7 @@ use crate::{Error, RANDOM_DEVICE_PATH, Result}; /// The salt generation converts random bytes to lowercase letters (a-z) /// for readability while maintaining sufficient entropy for security. pub async fn generate_salt( - virtual_file_system: &VirtualFileSystem<'_>, + virtual_file_system: &VirtualFileSystem, task: TaskIdentifier, ) -> Result { let mut buffer = [0_u8; 16]; @@ -81,7 +81,7 @@ pub async fn generate_salt( /// to collision attacks. The salt prevents rainbow table attacks and ensures /// that identical passwords have different hashes. pub async fn hash_password( - virtual_file_system: &VirtualFileSystem<'_>, + virtual_file_system: &VirtualFileSystem, task: TaskIdentifier, password: &str, salt: &str, diff --git a/modules/authentication/src/user.rs b/modules/authentication/src/user.rs index 619893d6..8fa5ddc5 100644 --- a/modules/authentication/src/user.rs +++ b/modules/authentication/src/user.rs @@ -197,8 +197,8 @@ pub fn get_user_file_path(user_name: &str) -> Result { /// - `Failed_to_read_user_file` - I/O error reading user file /// - `Failed_to_parse_user_file` - Invalid JSON format in user file /// - `Invalid_password` - Password doesn't match stored hash -pub async fn authenticate_user<'a>( - virtual_file_system: &'a VirtualFileSystem<'a>, +pub async fn authenticate_user( + virtual_file_system: &VirtualFileSystem, user_name: &str, password: &str, ) -> Result { @@ -263,8 +263,8 @@ pub async fn authenticate_user<'a>( /// - File system operations (directory creation, file writing) /// - Users manager operations (adding user) /// - Random salt generation failures -pub async fn create_user<'a>( - virtual_file_system: &'a VirtualFileSystem<'a>, +pub async fn create_user( + virtual_file_system: &VirtualFileSystem, user_name: &str, password: &str, primary_group: GroupIdentifier, @@ -355,8 +355,8 @@ pub async fn create_user<'a>( /// - File system errors (opening, reading, writing user file) /// - Salt generation failures /// - JSON parsing errors -pub async fn change_user_password<'a>( - virtual_file_system: &'a VirtualFileSystem<'a>, +pub async fn change_user_password( + virtual_file_system: &VirtualFileSystem, user_name: &str, new_password: &str, ) -> Result<()> { @@ -419,8 +419,8 @@ pub async fn change_user_password<'a>( /// - File system errors (opening, reading, writing user file) /// - JSON parsing errors /// - Path construction failures -pub async fn change_user_name<'a>( - virtual_file_system: &'a VirtualFileSystem<'a>, +pub async fn change_user_name( + virtual_file_system: &VirtualFileSystem, current_name: &str, new_name: &str, ) -> Result<()> { @@ -479,8 +479,8 @@ pub async fn change_user_name<'a>( /// - Path construction failures /// - File system errors (opening, reading) /// - JSON parsing errors -pub async fn read_user_file<'a>( - virtual_file_system: &'a VirtualFileSystem<'a>, +pub async fn read_user_file( + virtual_file_system: &VirtualFileSystem, buffer: &mut Vec, file: &str, ) -> Result { diff --git a/modules/bindings/host/tests/graphics.rs b/modules/bindings/host/tests/graphics.rs index dcd9f9e7..b1c96cbb 100644 --- a/modules/bindings/host/tests/graphics.rs +++ b/modules/bindings/host/tests/graphics.rs @@ -15,7 +15,7 @@ async fn test() { let binary_path = build_crate(&"host_bindings_wasm_test").unwrap(); let binary_buffer = fs::read(&binary_path).unwrap(); - let standard = testing::initialize(true).await.split(); + let standard = testing::initialize(true, false).await.split(); let virtual_machine = virtual_machine::initialize(&[&host_bindings::GraphicsBindings]); diff --git a/modules/executable/src/standard.rs b/modules/executable/src/standard.rs index 6319f514..7918568e 100644 --- a/modules/executable/src/standard.rs +++ b/modules/executable/src/standard.rs @@ -18,7 +18,7 @@ impl Standard { standard_out: &impl AsRef, standard_error: &impl AsRef, task: TaskIdentifier, - virtual_file_system: &'static VirtualFileSystem<'static>, + virtual_file_system: &'static VirtualFileSystem, ) -> Result { let standard_in = virtual_file_system .open(standard_in, AccessFlags::Read.into(), task) @@ -95,7 +95,7 @@ impl Standard { pub async fn close( self, - virtual_file_system: &VirtualFileSystem<'_>, + virtual_file_system: &VirtualFileSystem, ) -> virtual_file_system::Result<()> { self.standard_in.close(virtual_file_system).await?; self.standard_out.close(virtual_file_system).await?; diff --git a/modules/file_system/src/error.rs b/modules/file_system/src/error.rs index 3915d3bc..89e14ef1 100644 --- a/modules/file_system/src/error.rs +++ b/modules/file_system/src/error.rs @@ -116,6 +116,8 @@ pub enum Error { NotMounted, /// Already mounted. AlreadyMounted, + /// Invalid context + InvalidContext, /// Other unclassified error. Other, } @@ -221,6 +223,7 @@ impl Display for Error { Error::InvalidInode => "Invalid inode", Error::NotMounted => "Not mounted", Error::AlreadyMounted => "Already mounted", + Error::InvalidContext => "Invalid context", Error::Other => "Other", }; diff --git a/modules/file_system/src/fundamentals/entry.rs b/modules/file_system/src/fundamentals/entry.rs index d32f5848..ab6a847a 100644 --- a/modules/file_system/src/fundamentals/entry.rs +++ b/modules/file_system/src/fundamentals/entry.rs @@ -6,7 +6,7 @@ use alloc::string::String; -use crate::Kind; +use crate::{Kind, Path, PathOwned}; use super::{Inode, Size}; @@ -59,6 +59,10 @@ impl Entry { size, } } + + pub fn join_path(&self, base_path: impl AsRef) -> Option { + base_path.as_ref().join(Path::from_str(&self.name)) + } } impl AsMut<[u8]> for Entry { diff --git a/modules/file_system/src/fundamentals/path/components.rs b/modules/file_system/src/fundamentals/path/components.rs index 5a335014..ccab031c 100644 --- a/modules/file_system/src/fundamentals/path/components.rs +++ b/modules/file_system/src/fundamentals/path/components.rs @@ -30,6 +30,20 @@ impl<'a> Components<'a> { Components(path.as_str().split(SEPARATOR)) } + pub fn strip_prefix(self, components: &Components<'a>) -> Option> { + let mut self_iter = self.clone(); + let components_iter = components.clone(); + + for component in components_iter { + match self_iter.next() { + Some(self_component) if self_component == component => {} + _ => return None, + } + } + + Some(Components(self_iter.0)) + } + pub fn get_common_components(self, other: Components<'a>) -> usize { self.zip(other).take_while(|(a, b)| a == b).count() } diff --git a/modules/file_system/src/fundamentals/path/path_reference.rs b/modules/file_system/src/fundamentals/path/path_reference.rs index 81dc3cd3..3c729393 100644 --- a/modules/file_system/src/fundamentals/path/path_reference.rs +++ b/modules/file_system/src/fundamentals/path/path_reference.rs @@ -21,6 +21,7 @@ impl Path { /// Stores system-wide settings in a structured format (e.g., JSON, TOML). pub const DEVICES: &'static Path = Self::from_str("/devices"); + pub const NETWORK_DEVICES: &'static Path = Self::from_str("/devices/network"); /// Hardware devices, symlinks for human-friendly names. pub const CONFIGURATION: &'static Path = Self::from_str("/configuration"); @@ -188,7 +189,7 @@ impl Path { Components::new(self) } - pub fn join(&self, path: &Path) -> Option { + pub fn join(&self, path: impl AsRef) -> Option { self.to_owned().join(path) } diff --git a/modules/graphics/fonts_generator/fonts/Font Awesome 7 Free-Solid-900.otf b/modules/graphics/fonts_generator/fonts/Font Awesome 7 Free-Solid-900.otf new file mode 100644 index 00000000..881f5d3d Binary files /dev/null and b/modules/graphics/fonts_generator/fonts/Font Awesome 7 Free-Solid-900.otf differ diff --git a/modules/graphics/fonts_generator/fonts/FontAwesome7-Solid+Brands+Regular.otf b/modules/graphics/fonts_generator/fonts/FontAwesome7-Solid+Brands+Regular.otf new file mode 100644 index 00000000..184f7569 Binary files /dev/null and b/modules/graphics/fonts_generator/fonts/FontAwesome7-Solid+Brands+Regular.otf differ diff --git a/modules/graphics/fonts_generator/fonts/FontAwesome7-Solid.woff2 b/modules/graphics/fonts_generator/fonts/FontAwesome7-Solid.woff2 new file mode 100644 index 00000000..c20c7f4f Binary files /dev/null and b/modules/graphics/fonts_generator/fonts/FontAwesome7-Solid.woff2 differ diff --git a/modules/graphics/fonts_generator/src/main.rs b/modules/graphics/fonts_generator/src/main.rs index eb400b4a..68c09971 100644 --- a/modules/graphics/fonts_generator/src/main.rs +++ b/modules/graphics/fonts_generator/src/main.rs @@ -17,7 +17,7 @@ pub const FONT_AWESOME_RANGE: &[u32] = &[ 61479, 61480, 61502, 61512, 61515, 61516, 61517, 61521, 61522, 61523, 61524, 61543, 61544, 61550, 61552, 61553, 61556, 61559, 61560, 61561, 61563, 61587, 61589, 61636, 61637, 61639, 61671, 61674, 61683, 61724, 61732, 61787, 61931, 62016, 62017, 62018, 62019, 62020, 62087, - 62099, 62212, 62189, 62810, 63426, 63650, + 62099, 62212, 62189, 62810, 63231, 63426, 63650, ]; pub const FONTS_DIRECTORY: &str = "fonts"; pub const GENERATED_FONTS_DIRECTORY: &str = "generated_fonts"; @@ -156,6 +156,7 @@ fn main() { let font_path = fonts_directory.join(path); let font_awesome_path = if *enable_fontawesome { Some(fonts_directory.join("FontAwesome5-Solid+Brands+Regular.woff")) + //Some(fonts_directory.join("FontAwesome7-Solid+Brands+Regular.otf")) } else { None }; diff --git a/modules/graphics/src/lib.rs b/modules/graphics/src/lib.rs index 578d1474..7a350097 100644 --- a/modules/graphics/src/lib.rs +++ b/modules/graphics/src/lib.rs @@ -14,7 +14,7 @@ pub mod macros; mod manager; mod point; mod screen; -pub mod symbols; +pub mod symbol; mod window; pub mod lvgl; diff --git a/modules/graphics/src/lvgl.rs b/modules/graphics/src/lvgl.rs index 751ac1a8..8bf68100 100644 --- a/modules/graphics/src/lvgl.rs +++ b/modules/graphics/src/lvgl.rs @@ -94,3 +94,48 @@ pub unsafe fn lv_tabview_add_tab(tabview: *mut lv_obj_t, name: *const c_char) -> page } } + +unsafe extern "C" fn radio_event_handler(event: *mut lv_event_t) { + unsafe { + let code = lvgl_rust_sys::lv_event_get_code(event); + let target = lvgl_rust_sys::lv_event_get_target(event) as *mut lv_obj_t; + let user_data = lvgl_rust_sys::lv_event_get_user_data(event) as *mut lv_obj_t; + + if code == lv_event_code_t_LV_EVENT_CLICKED { + let parent = user_data as *mut lv_obj_t; + let child_count = lvgl_rust_sys::lv_obj_get_child_count(parent); + + for i in 0..child_count { + let child = lvgl_rust_sys::lv_obj_get_child(parent, i as _); + if child != target { + lvgl_rust_sys::lv_obj_remove_state(child, lvgl_rust_sys::LV_STATE_CHECKED as _); + } + } + + lvgl_rust_sys::lv_obj_add_state(target, lvgl_rust_sys::LV_STATE_CHECKED as _); + } + } +} + +/// Create a radio button (a checkbox that behaves like a radio button) +/// +/// # Arguments +/// +/// * `parent` - The parent object of the radio button. +/// +/// # Safety +/// This function is unsafe because it may dereference raw pointers (e.g. `parent`). +pub unsafe fn lv_radiobox_create(parent: *mut lv_obj_t) -> *mut lv_obj_t { + unsafe { + let checkbox = lvgl_rust_sys::lv_checkbox_create(parent); + + lvgl_rust_sys::lv_obj_add_event_cb( + checkbox, + Some(radio_event_handler), + lvgl_rust_sys::lv_event_code_t_LV_EVENT_CLICKED, + parent as _, + ); + + checkbox + } +} diff --git a/modules/graphics/src/macros.rs b/modules/graphics/src/macros.rs index 2400d767..52ba90a2 100644 --- a/modules/graphics/src/macros.rs +++ b/modules/graphics/src/macros.rs @@ -12,7 +12,7 @@ macro_rules! lock { macro_rules! synchronous_lock { ($body:block) => {{ let _lock = $crate::get_instance().synchronous_lock(); - let __result = { $($body)* }; + let __result = { $body }; ::core::mem::drop(_lock); __result }}; diff --git a/modules/graphics/src/symbol.rs b/modules/graphics/src/symbol.rs new file mode 100644 index 00000000..0242f0da --- /dev/null +++ b/modules/graphics/src/symbol.rs @@ -0,0 +1,86 @@ +use core::ffi::CStr; + +use crate::lvgl; + +// LVGL symbols +pub const BULLET: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_BULLET) }; +pub const AUDIO: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_AUDIO) }; +pub const VIDEO: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_VIDEO) }; +pub const LIST: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_LIST) }; +pub const OK: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_OK) }; +pub const CLOSE: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_CLOSE) }; +pub const POWER: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_POWER) }; +pub const SETTINGS: &CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_SETTINGS) }; +pub const HOME: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_HOME) }; +pub const DOWNLOAD: &CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_DOWNLOAD) }; +pub const DRIVE: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_DRIVE) }; +pub const REFRESH: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_REFRESH) }; +pub const MUTE: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_MUTE) }; +pub const VOLUME_MID: &CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_VOLUME_MID) }; +pub const VOLUME_MAX: &CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_VOLUME_MAX) }; +pub const IMAGE: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_IMAGE) }; +pub const TINT: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_TINT) }; +pub const PREV: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_PREV) }; +pub const PLAY: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_PLAY) }; +pub const PAUSE: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_PAUSE) }; +pub const STOP: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_STOP) }; +pub const NEXT: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_NEXT) }; +pub const EJECT: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_EJECT) }; +pub const LEFT: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_LEFT) }; +pub const RIGHT: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_RIGHT) }; +pub const PLUS: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_PLUS) }; +pub const MINUS: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_MINUS) }; +pub const EYE_OPEN: &CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_EYE_OPEN) }; +pub const EYE_CLOSE: &CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_EYE_CLOSE) }; +pub const WARNING: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_WARNING) }; +pub const SHUFFLE: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_SHUFFLE) }; +pub const UP: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_UP) }; +pub const DOWN: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_DOWN) }; +pub const LOOP: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_LOOP) }; +pub const DIRECTORY: &CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_DIRECTORY) }; +pub const UPLOAD: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_UPLOAD) }; +pub const CALL: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_CALL) }; +pub const CUT: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_CUT) }; +pub const COPY: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_COPY) }; +pub const SAVE: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_SAVE) }; +pub const BARS: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_BARS) }; +pub const ENVELOPE: &CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_ENVELOPE) }; +pub const CHARGE: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_CHARGE) }; +pub const PASTE: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_PASTE) }; +pub const BELL: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_BELL) }; +pub const KEYBOARD: &CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_KEYBOARD) }; +pub const GPS: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_GPS) }; +pub const FILE: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_FILE) }; +pub const WIFI: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_WIFI) }; +pub const BATTERY_FULL: &CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_BATTERY_FULL) }; +pub const BATTERY_3: &CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_BATTERY_3) }; +pub const BATTERY_2: &CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_BATTERY_2) }; +pub const BATTERY_1: &CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_BATTERY_1) }; +pub const BATTERY_EMPTY: &CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_BATTERY_EMPTY) }; +pub const USB: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_USB) }; +pub const BLUETOOTH: &CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_BLUETOOTH) }; +pub const TRASH: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_TRASH) }; +pub const EDIT: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_EDIT) }; +pub const BACKSPACE: &CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_BACKSPACE) }; +pub const SD_CARD: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_SD_CARD) }; +pub const NEW_LINE: &CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_NEW_LINE) }; +pub const DUMMY: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(lvgl::LV_SYMBOL_DUMMY) }; +// Additional symbols +pub const NETWORK_WIRED: &CStr = c"\xEF\x9B\xBF"; diff --git a/modules/network/Cargo.toml b/modules/network/Cargo.toml index 39b2301a..1f676896 100644 --- a/modules/network/Cargo.toml +++ b/modules/network/Cargo.toml @@ -5,12 +5,39 @@ edition = "2024" [dependencies] file_system = { workspace = true } - synchronization = { workspace = true } time = { workspace = true } -embassy-net = { workspace = true, features = [ +task = { workspace = true } +users = { workspace = true } +log = { workspace = true } +virtual_file_system = { workspace = true } +shared = { workspace = true } + +embedded-io-async = { workspace = true } +embassy-time = { workspace = true } +embassy-futures = { workspace = true } +smol_str = { workspace = true } +smoltcp = { workspace = true, features = [ + "async", + "alloc", + "log", + "medium-ethernet", + "medium-ip", + "packetmeta-id", "proto-ipv4", "proto-ipv6", - "dhcpv4", - "dhcpv4-hostname", + "socket-raw", + "socket-udp", + "socket-tcp", + "socket-dns", + "socket-icmp", + "socket-dhcpv4", ] } +heapless = { version = "0.8" } + +[dev-dependencies] +testing = { workspace = true } +drivers_std = { workspace = true } +drivers_shared = { workspace = true } +little_fs = { workspace = true } +abi_definitions = { workspace = true } diff --git a/modules/network/src/device/command.rs b/modules/network/src/device/command.rs new file mode 100644 index 00000000..38df74ff --- /dev/null +++ b/modules/network/src/device/command.rs @@ -0,0 +1,203 @@ +use crate::{InterfaceKind, IpAddress, IpCidr, MacAddress, Route}; +use file_system::{ControlCommand, define_command}; + +#[repr(C)] +pub struct WifiClientConfiguration { + // TODO: Add fields +} + +define_command!(GET_KIND, Read, b'n', 1, (), InterfaceKind); + +#[repr(u8)] +enum CommandNumber { + SetState, + GetState, + GetHardwareAddress, + SetHardwareAddress, + GetMtu, + GetMaximumBurstSize, + IsLinkUp, + GetRouteCount, + GetRoute, + AddRoute, + RemoveRoute, + GetDnsServerCount, + GetDnsServer, + AddDnsServer, + RemoveDnsServer, + SetDhcpState, + GetDhcpState, + GetIpAddressCount, + GetIpAddress, + AddIpAddress, + RemoveIpAddress, +} + +define_command!( + SET_STATE, + Write, + b'n', + CommandNumber::SetState as u8, + bool, + () +); +define_command!( + GET_STATE, + Read, + b'n', + CommandNumber::GetState as u8, + (), + bool +); +define_command!( + GET_HARDWARE_ADDRESS, + Read, + b'n', + CommandNumber::GetHardwareAddress as u8, + (), + MacAddress +); +define_command!( + SET_HARDWARE_ADDRESS, + Write, + b'n', + CommandNumber::SetHardwareAddress as u8, + MacAddress, + () +); +define_command!( + GET_MAXIMUM_TRANSMISSION_UNIT, + Read, + b'n', + CommandNumber::GetMtu as u8, + (), + usize +); +define_command!( + GET_MAXIMUM_BURST_SIZE, + Read, + b'n', + CommandNumber::GetMaximumBurstSize as u8, + (), + Option +); +define_command!( + IS_LINK_UP, + Read, + b'n', + CommandNumber::IsLinkUp as u8, + (), + bool +); +define_command!( + GET_ROUTE_COUNT, + Read, + b'n', + CommandNumber::GetRouteCount as u8, + (), + usize +); +define_command!( + GET_ROUTE, + Read, + b'n', + CommandNumber::GetRoute as u8, + usize, + Route +); +define_command!( + ADD_ROUTE, + Write, + b'n', + CommandNumber::AddRoute as u8, + Route, + () +); +define_command!( + REMOVE_ROUTE, + Write, + b'n', + CommandNumber::RemoveRoute as u8, + usize, + () +); +define_command!( + GET_DNS_SERVER_COUNT, + Read, + b'n', + CommandNumber::GetDnsServerCount as u8, + (), + usize +); +define_command!( + GET_DNS_SERVER, + Read, + b'n', + CommandNumber::GetDnsServer as u8, + usize, + IpAddress +); +define_command!( + ADD_DNS_SERVER, + Write, + b'n', + CommandNumber::AddDnsServer as u8, + IpAddress, + () +); +define_command!( + REMOVE_DNS_SERVER, + Write, + b'n', + CommandNumber::RemoveDnsServer as u8, + usize, + () +); +define_command!( + SET_DHCP_STATE, + Write, + b'n', + CommandNumber::SetDhcpState as u8, + bool, + () +); +define_command!( + GET_DHCP_STATE, + Read, + b'n', + CommandNumber::GetDhcpState as u8, + (), + bool +); +define_command!( + GET_IP_ADDRESS_COUNT, + Read, + b'n', + CommandNumber::GetIpAddressCount as u8, + (), + usize +); +define_command!( + GET_IP_ADDRESS, + Read, + b'n', + CommandNumber::GetIpAddress as u8, + usize, + IpCidr +); +define_command!( + ADD_IP_ADDRESS, + Write, + b'n', + CommandNumber::AddIpAddress as u8, + IpCidr, + () +); +define_command!( + REMOVE_IP_ADDRESS, + Write, + b'n', + CommandNumber::RemoveIpAddress as u8, + usize, + () +); diff --git a/modules/network/src/device/loopback.rs b/modules/network/src/device/loopback.rs new file mode 100644 index 00000000..7577abdb --- /dev/null +++ b/modules/network/src/device/loopback.rs @@ -0,0 +1,44 @@ +use crate::{GET_KIND, InterfaceKind}; +use file_system::{ + ControlCommand, ControlCommandIdentifier, DirectBaseOperations, DirectCharacterDevice, Error, + MountOperations, +}; +use shared::AnyByLayout; +pub use smoltcp::phy::Loopback; +use smoltcp::phy::Medium; + +pub struct LoopbackControllerDevice; + +impl DirectBaseOperations for LoopbackControllerDevice { + fn read(&self, _: &mut [u8], _: file_system::Size) -> file_system::Result { + Err(Error::UnsupportedOperation) + } + + fn write(&self, _: &[u8], _: file_system::Size) -> file_system::Result { + Err(Error::UnsupportedOperation) + } + + fn control( + &self, + command: ControlCommandIdentifier, + _: &AnyByLayout, + output: &mut AnyByLayout, + ) -> file_system::Result<()> { + match command { + GET_KIND::IDENTIFIER => { + let kind = GET_KIND::cast_output(output)?; + *kind = InterfaceKind::Ethernet; + Ok(()) + } + _ => Err(Error::UnsupportedOperation), + } + } +} + +impl MountOperations for LoopbackControllerDevice {} + +impl DirectCharacterDevice for LoopbackControllerDevice {} + +pub fn create_loopback_device() -> (Loopback, LoopbackControllerDevice) { + (Loopback::new(Medium::Ethernet), LoopbackControllerDevice) +} diff --git a/modules/network/src/device/mod.rs b/modules/network/src/device/mod.rs new file mode 100644 index 00000000..fd4d27dc --- /dev/null +++ b/modules/network/src/device/mod.rs @@ -0,0 +1,5 @@ +mod command; +mod loopback; + +pub use command::*; +pub use loopback::*; diff --git a/modules/network/src/error.rs b/modules/network/src/error.rs index 6d40c803..cce9bc36 100644 --- a/modules/network/src/error.rs +++ b/modules/network/src/error.rs @@ -1,4 +1,5 @@ use core::{fmt::Display, num::NonZeroU8}; +use smoltcp::socket::{dns, icmp, udp}; pub type Result = core::result::Result; @@ -30,17 +31,38 @@ pub enum Error { Unsupported, UnexpectedEndOfFile, OutOfMemory, - InProgress, - PoisonnedLock, + Pending, UnsupportedProtocol, InvalidIdentifier, DuplicateIdentifier, + FailedToGenerateSeed(file_system::Error), + FailedToSpawnNetworkTask(task::Error), + // - DNS + InvalidName, + NameTooLong, + Failed, + // - Accept / Connect + InvalidState, + InvalidPort, + NoRoute, + // Udp + Truncated, + SocketNotBound, + PacketTooLarge, + InvalidEndpoint, + + FailedToMountDevice(virtual_file_system::Error), + + NoFreeSlot, + Other, } +impl core::error::Error for Error {} + impl Error { pub const fn get_discriminant(&self) -> NonZeroU8 { - unsafe { NonZeroU8::new_unchecked(*self as u8) } + unsafe { *(self as *const Self as *const NonZeroU8) } } } @@ -78,8 +100,7 @@ impl Display for Error { Error::Unsupported => write!(f, "Unsupported operation"), Error::UnexpectedEndOfFile => write!(f, "Unexpected end of file"), Error::OutOfMemory => write!(f, "Out of memory"), - Error::InProgress => write!(f, "In progress operation not completed yet"), - Error::PoisonnedLock => write!(f, "Poisoned lock encountered an error state"), + Error::Pending => write!(f, "In progress operation not completed yet"), Error::UnsupportedProtocol => write!(f, "Unsupported protocol used in operation"), Error::InvalidIdentifier => { write!(f, "Invalid identifier provided for operation") @@ -87,7 +108,73 @@ impl Display for Error { Error::DuplicateIdentifier => { write!(f, "Duplicate identifier found in operation") } + Error::FailedToGenerateSeed(e) => { + write!(f, "Failed to generate seed: {}", e) + } + Error::FailedToSpawnNetworkTask(e) => { + write!(f, "Failed to spawn network task: {}", e) + } + Error::InvalidName => write!(f, "Invalid name"), + Error::NameTooLong => write!(f, "Name too long"), + Error::Failed => write!(f, "Failed"), + Error::InvalidState => write!(f, "Invalid state for operation"), + Error::InvalidPort => write!(f, "Invalid port specified"), + Error::NoRoute => write!(f, "No route to host"), + Error::Truncated => write!(f, "Truncated packet received"), + Error::SocketNotBound => write!(f, "Socket not bound"), + Error::PacketTooLarge => write!(f, "Packet too large to send"), + Error::InvalidEndpoint => write!(f, "Invalid endpoint specified"), + Error::FailedToMountDevice(e) => { + write!(f, "Failed to mount device: {}", e) + } + Error::NoFreeSlot => write!(f, "No free slot available"), Error::Other => write!(f, "Other error occurred"), } } } + +impl From for Error { + fn from(e: dns::StartQueryError) -> Self { + match e { + dns::StartQueryError::InvalidName => Error::InvalidName, + dns::StartQueryError::NameTooLong => Error::NameTooLong, + dns::StartQueryError::NoFreeSlot => Error::NoFreeSlot, + } + } +} + +impl From for Error { + fn from(e: dns::GetQueryResultError) -> Self { + match e { + dns::GetQueryResultError::Pending => Error::Pending, + dns::GetQueryResultError::Failed => Error::Failed, + } + } +} + +impl From for Error { + fn from(e: icmp::SendError) -> Self { + match e { + icmp::SendError::Unaddressable => Error::InvalidEndpoint, + icmp::SendError::BufferFull => Error::ResourceBusy, + } + } +} + +impl From for Error { + fn from(e: icmp::BindError) -> Self { + match e { + icmp::BindError::InvalidState => Error::InvalidState, + icmp::BindError::Unaddressable => Error::NoRoute, + } + } +} + +impl From for Error { + fn from(e: udp::BindError) -> Self { + match e { + udp::BindError::InvalidState => Error::InvalidState, + udp::BindError::Unaddressable => Error::NoRoute, + } + } +} diff --git a/modules/network/src/fundamentals/cidr.rs b/modules/network/src/fundamentals/cidr.rs new file mode 100644 index 00000000..65e46d74 --- /dev/null +++ b/modules/network/src/fundamentals/cidr.rs @@ -0,0 +1,103 @@ +use core::fmt::{Debug, Display}; + +use crate::{Ipv4, Ipv6}; + +#[repr(C)] +#[derive(Default, Clone, PartialEq, Eq)] +pub struct Cidr { + pub address: T, + pub prefix_length: u8, +} + +impl Cidr { + pub const fn new(address: T, prefix_length: u8) -> Self { + Self { + address, + prefix_length, + } + } +} + +impl Debug for Cidr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:?}/{}", self.address, self.prefix_length) + } +} + +impl Display for Cidr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}/{}", self.address, self.prefix_length) + } +} + +impl Cidr { + pub const fn into_smoltcp(&self) -> smoltcp::wire::Ipv4Cidr { + smoltcp::wire::Ipv4Cidr::new(self.address.into_smoltcp(), self.prefix_length) + } + + pub const fn from_smoltcp(value: &smoltcp::wire::Ipv4Cidr) -> Self { + Self { + address: Ipv4::from_smoltcp(&value.address()), + prefix_length: value.prefix_len(), + } + } +} + +impl Cidr { + pub const fn into_smoltcp(&self) -> smoltcp::wire::Ipv6Cidr { + smoltcp::wire::Ipv6Cidr::new(self.address.into_smoltcp(), self.prefix_length) + } + + pub const fn from_smoltcp(value: &smoltcp::wire::Ipv6Cidr) -> Self { + Self { + address: Ipv6::from_smoltcp(&value.address()), + prefix_length: value.prefix_len(), + } + } +} + +#[repr(C)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum IpCidr { + IPv4(Cidr), + IPv6(Cidr), +} + +impl Default for IpCidr { + fn default() -> Self { + IpCidr::IPv4(Cidr::default()) + } +} + +impl IpCidr { + pub const fn new_ipv4(address: [u8; 4], prefix_length: u8) -> Self { + IpCidr::IPv4(Cidr::new(Ipv4::new(address), prefix_length)) + } + + pub const fn new_ipv6(address: [u16; 8], prefix_length: u8) -> Self { + IpCidr::IPv6(Cidr::new(Ipv6::new(address), prefix_length)) + } + + pub const fn into_smoltcp(&self) -> smoltcp::wire::IpCidr { + match self { + IpCidr::IPv4(cidr) => smoltcp::wire::IpCidr::Ipv4(cidr.into_smoltcp()), + IpCidr::IPv6(cidr) => smoltcp::wire::IpCidr::Ipv6(cidr.into_smoltcp()), + } + } + + pub const fn from_smoltcp(value: &smoltcp::wire::IpCidr) -> Self { + match value { + smoltcp::wire::IpCidr::Ipv4(cidr) => IpCidr::IPv4(Cidr::::from_smoltcp(cidr)), + smoltcp::wire::IpCidr::Ipv6(cidr) => IpCidr::IPv6(Cidr::::from_smoltcp(cidr)), + } + } +} + +impl Display for IpCidr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + IpCidr::IPv4(cidr) => write!(f, "{cidr}"), + IpCidr::IPv6(cidr) => write!(f, "{cidr}"), + } + } +} diff --git a/modules/network/src/fundamentals/dns.rs b/modules/network/src/fundamentals/dns.rs new file mode 100644 index 00000000..ae11a100 --- /dev/null +++ b/modules/network/src/fundamentals/dns.rs @@ -0,0 +1,11 @@ +use shared::flags; + +flags! { + pub enum DnsQueryKind: u8 { + A, + Aaaa, + Cname, + Ns, + Soa, + } +} diff --git a/modules/network/src/fundamentals/duration.rs b/modules/network/src/fundamentals/duration.rs new file mode 100644 index 00000000..e27395ac --- /dev/null +++ b/modules/network/src/fundamentals/duration.rs @@ -0,0 +1,80 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +pub struct Duration(u64); + +impl Duration { + pub const MINIMUM: Self = Self(u64::MIN); + pub const MAXIMUM: Self = Self(u64::MAX); + + pub const fn from_seconds(seconds: u64) -> Self { + Self(embassy_time::Duration::from_secs(seconds).as_ticks()) + } + + pub const fn from_milliseconds(milliseconds: u64) -> Self { + Self(embassy_time::Duration::from_millis(milliseconds).as_ticks()) + } + + pub const fn from_microseconds(microseconds: u64) -> Self { + Self(embassy_time::Duration::from_micros(microseconds).as_ticks()) + } + + pub const fn from_nanoseconds(nanoseconds: u64) -> Self { + Self(embassy_time::Duration::from_nanos(nanoseconds).as_ticks()) + } + + pub const fn as_ticks(&self) -> u64 { + self.0 + } + + pub const fn as_seconds(&self) -> u64 { + embassy_time::Duration::from_ticks(self.0).as_secs() + } + + pub const fn as_milliseconds(&self) -> u64 { + embassy_time::Duration::from_ticks(self.0).as_millis() + } + + pub const fn as_microseconds(&self) -> u64 { + embassy_time::Duration::from_ticks(self.0).as_micros() + } + + pub const fn into_smoltcp(self) -> smoltcp::time::Duration { + smoltcp::time::Duration::from_micros(self.as_microseconds()) + } + + pub const fn into_embassy(self) -> embassy_time::Duration { + embassy_time::Duration::from_ticks(self.0) + } + + pub const fn from_embassy(value: embassy_time::Duration) -> Self { + Self(value.as_ticks()) + } + + pub const fn from_smoltcp(value: smoltcp::time::Duration) -> Self { + Self::from_microseconds(value.micros()) + } +} + +impl From for Duration { + fn from(value: embassy_time::Duration) -> Self { + Self::from_embassy(value) + } +} + +impl From for embassy_time::Duration { + fn from(value: Duration) -> Self { + value.into_embassy() + } +} + +impl From for Duration { + fn from(value: core::time::Duration) -> Self { + Self::from_microseconds(value.as_micros() as u64) + } +} + +impl From for core::time::Duration { + fn from(value: Duration) -> Self { + core::time::Duration::from_micros(value.as_microseconds()) + } +} diff --git a/modules/network/src/fundamentals/endpoint.rs b/modules/network/src/fundamentals/endpoint.rs new file mode 100644 index 00000000..b15596bd --- /dev/null +++ b/modules/network/src/fundamentals/endpoint.rs @@ -0,0 +1,67 @@ +use smoltcp::wire; + +use crate::{IpAddress, Port}; + +#[repr(C)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct IpEndpoint { + pub address: IpAddress, + pub port: Port, +} + +impl IpEndpoint { + pub const fn new(address: IpAddress, port: Port) -> Self { + Self { address, port } + } + + pub const fn into_smoltcp(&self) -> wire::IpEndpoint { + wire::IpEndpoint { + addr: self.address.into_smoltcp(), + port: self.port.into_inner(), + } + } + + pub const fn from_smoltcp(value: &wire::IpEndpoint) -> Self { + Self { + address: IpAddress::from_smoltcp(&value.addr), + port: Port::from_inner(value.port), + } + } +} + +#[repr(C)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct IpListenEndpoint { + pub address: Option, + pub port: Port, +} + +impl IpListenEndpoint { + pub const fn new(address: Option, port: Port) -> Self { + Self { address, port } + } + + pub const fn into_smoltcp(&self) -> wire::IpListenEndpoint { + let smoltcp_address = match &self.address { + Some(ip) => Some(ip.into_smoltcp()), + None => None, + }; + + wire::IpListenEndpoint { + addr: smoltcp_address, + port: self.port.into_inner(), + } + } + + pub const fn from_smoltcp(value: &wire::IpListenEndpoint) -> Self { + let address = match &value.addr { + Some(ip) => Some(IpAddress::from_smoltcp(ip)), + None => None, + }; + + Self { + address, + port: Port::from_inner(value.port), + } + } +} diff --git a/modules/network/src/fundamentals/ip/ipv4.rs b/modules/network/src/fundamentals/ip/ipv4.rs new file mode 100644 index 00000000..cde9f9f8 --- /dev/null +++ b/modules/network/src/fundamentals/ip/ipv4.rs @@ -0,0 +1,113 @@ +use core::fmt::Display; + +use crate::Ipv6; + +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[repr(transparent)] +pub struct Ipv4([u8; 4]); + +impl Ipv4 { + pub const LOCALHOST: Self = Self([127, 0, 0, 1]); + pub const BROADCAST: Self = Self([255, 255, 255, 255]); + + pub const fn new(value: [u8; 4]) -> Self { + Self(value) + } + + pub const fn into_inner(self) -> [u8; 4] { + self.0 + } + + pub const fn from_inner(value: [u8; 4]) -> Self { + Self(value) + } + + pub const fn is_multicast(&self) -> bool { + self.0[0] >= 224 && self.0[0] <= 239 + } + + pub const fn is_broadcast(&self) -> bool { + u32::from_be_bytes(self.0) == u32::from_be_bytes(Self::BROADCAST.0) + } + + pub const fn to_ipv6_mapped(&self) -> Ipv6 { + Ipv6::new([ + 0, + 0, + 0, + 0, + 0, + 0xFFFF, + u16::from_be_bytes([self.0[0], self.0[1]]), + u16::from_be_bytes([self.0[2], self.0[3]]), + ]) + } + + pub const fn into_smoltcp(self) -> core::net::Ipv4Addr { + core::net::Ipv4Addr::new(self.0[0], self.0[1], self.0[2], self.0[3]) + } + + pub const fn from_smoltcp(value: &core::net::Ipv4Addr) -> Self { + Self(value.octets()) + } +} + +impl TryFrom<&str> for Ipv4 { + type Error = (); + + fn try_from(value: &str) -> Result { + let mut result = [0; 4]; + let mut index = 0; + + for part in value.split('.') { + if index >= 4 { + return Err(()); + } + let part = part.parse::().map_err(|_| ())?; + result[index] = part; + index += 1; + } + if index != 4 { + return Err(()); + } + + Ok(Self::new(result)) + } +} + +impl Display for Ipv4 { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}.{}.{}.{}", self.0[0], self.0[1], self.0[2], self.0[3]) + } +} + +#[cfg(test)] +mod tests { + use alloc::string::ToString; + + use super::*; + + #[test] + fn test_ipv4_display() { + let ip = Ipv4::new([192, 168, 1, 1]); + + assert_eq!(ip.to_string(), "192.168.1.1"); + } + + #[test] + fn test_ipv4_try_from() { + let ip = Ipv4::try_from("0.0.0.0").unwrap(); + + assert_eq!(ip.0, [0, 0, 0, 0]); + + Ipv4::try_from("1.2b.3.4").unwrap_err(); + + Ipv4::try_from("1.2.3.4.5").unwrap_err(); + + Ipv4::try_from("1.2.3").unwrap_err(); + + let ip = Ipv4::try_from("4.3.2.1").unwrap(); + + assert_eq!(ip.0, [4, 3, 2, 1]); + } +} diff --git a/modules/network/src/fundamentals/ip/ipv6.rs b/modules/network/src/fundamentals/ip/ipv6.rs new file mode 100644 index 00000000..53af63b7 --- /dev/null +++ b/modules/network/src/fundamentals/ip/ipv6.rs @@ -0,0 +1,119 @@ +use core::fmt::Display; + +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[repr(transparent)] +pub struct Ipv6([u8; 16]); // Avoid 2 byte alignment issues + +impl Ipv6 { + pub const fn new(value: [u16; 8]) -> Self { + let mut bytes = [0; 16]; + let mut i = 0; + while i < 8 { + let segment = value[i].to_be_bytes(); + bytes[i * 2] = segment[0]; + bytes[i * 2 + 1] = segment[1]; + i += 1; + } + Self(bytes) + } + + pub const fn into_inner(self) -> [u8; 16] { + self.0 + } + + pub const fn from_inner(value: [u8; 16]) -> Self { + Self(value) + } + + pub const fn into_smoltcp(self) -> core::net::Ipv6Addr { + core::net::Ipv6Addr::from_octets(self.0) + } + + pub const fn from_smoltcp(value: &core::net::Ipv6Addr) -> Self { + Self(value.octets()) + } +} + +impl TryFrom<&str> for Ipv6 { + type Error = (); + + fn try_from(value: &str) -> Result { + let mut result = [0; 8]; + let mut index = 0; + + for part in value.split(':') { + if index >= 8 { + return Err(()); + } + let part = u16::from_str_radix(part, 16).map_err(|_| ())?; + result[index] = part; + index += 1; + } + + if index != 8 { + return Err(()); + } + + Ok(Self::new(result)) + } +} + +impl Display for Ipv6 { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "{:x}{:x}:{:x}{:x}:{:x}{:x}:{:x}{:x}:{:x}{:x}:{:x}{:x}:{:x}{:x}:{:x}{:x}", + self.0[0], + self.0[1], + self.0[2], + self.0[3], + self.0[4], + self.0[5], + self.0[6], + self.0[7], + self.0[8], + self.0[9], + self.0[10], + self.0[11], + self.0[12], + self.0[13], + self.0[14], + self.0[15] + ) + } +} + +#[cfg(test)] +mod tests { + use alloc::string::ToString; + + use super::*; + + #[test] + fn test_ipv6_display() { + let ip = Ipv6::new([0; 8]); + + assert_eq!(ip.to_string(), "00:00:00:00:00:00:00:00"); + } + + #[test] + fn test_ipv6_try_from() { + let ip = Ipv6::try_from("0:0:0:0:0:0:0:0").unwrap(); + + assert_eq!(ip.0, [0; 16]); + + Ipv6::try_from("0:0:0:0:0:0:0:0:0").unwrap_err(); + + Ipv6::try_from("0:0:0:0:0:0:0").unwrap_err(); + + let ip = Ipv6::try_from("1234:5678:9abc:def0:1234:5678:9abc:def0").unwrap(); + + assert_eq!( + ip.0, + [ + 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, + 0xde, 0xf0 + ] + ); + } +} diff --git a/modules/network/src/fundamentals/ip/mod.rs b/modules/network/src/fundamentals/ip/mod.rs new file mode 100644 index 00000000..8dd9f6d4 --- /dev/null +++ b/modules/network/src/fundamentals/ip/mod.rs @@ -0,0 +1,119 @@ +mod ipv4; +mod ipv6; + +use core::fmt::Display; + +pub use ipv4::*; +pub use ipv6::*; + +#[repr(C)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum IpAddress { + IPv4(Ipv4), + IPv6(Ipv6), +} + +impl Default for IpAddress { + fn default() -> Self { + IpAddress::IPv4(Ipv4::default()) + } +} + +impl IpAddress { + pub const fn new_ipv4(value: [u8; 4]) -> Self { + Self::IPv4(Ipv4::new(value)) + } + + pub const fn new_ipv6(value: [u16; 8]) -> Self { + Self::IPv6(Ipv6::new(value)) + } + + pub const fn into_smoltcp(&self) -> smoltcp::wire::IpAddress { + match self { + IpAddress::IPv4(value) => smoltcp::wire::IpAddress::Ipv4(value.into_smoltcp()), + IpAddress::IPv6(value) => smoltcp::wire::IpAddress::Ipv6(value.into_smoltcp()), + } + } + + pub const fn from_smoltcp(value: &smoltcp::wire::IpAddress) -> Self { + match value { + smoltcp::wire::IpAddress::Ipv4(v4_addr) => { + IpAddress::IPv4(crate::Ipv4::from_smoltcp(v4_addr)) + } + smoltcp::wire::IpAddress::Ipv6(v6_addr) => { + IpAddress::IPv6(crate::Ipv6::from_smoltcp(v6_addr)) + } + } + } +} + +impl TryFrom<&str> for IpAddress { + type Error = (); + + fn try_from(value: &str) -> Result { + if let Ok(ipv4) = Ipv4::try_from(value) { + Ok(IpAddress::IPv4(ipv4)) + } else if let Ok(ipv6) = Ipv6::try_from(value) { + Ok(IpAddress::IPv6(ipv6)) + } else { + Err(()) + } + } +} + +impl From<[u8; 4]> for IpAddress { + fn from(value: [u8; 4]) -> Self { + IpAddress::IPv4(Ipv4::new(value)) + } +} + +impl From<&[u8; 4]> for IpAddress { + fn from(value: &[u8; 4]) -> Self { + IpAddress::IPv4(Ipv4::new(*value)) + } +} + +impl From<[u8; 16]> for IpAddress { + fn from(value: [u8; 16]) -> Self { + IpAddress::IPv6(Ipv6::from_inner(value)) + } +} + +impl From<&[u8; 16]> for IpAddress { + fn from(value: &[u8; 16]) -> Self { + IpAddress::IPv6(Ipv6::from_inner(*value)) + } +} + +impl From for smoltcp::wire::IpAddress { + fn from(value: IpAddress) -> Self { + value.into_smoltcp() + } +} + +impl From for IpAddress { + fn from(value: smoltcp::wire::IpAddress) -> Self { + IpAddress::from_smoltcp(&value) + } +} + +impl Display for IpAddress { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + IpAddress::IPv4(value) => write!(f, "{value}"), + IpAddress::IPv6(value) => write!(f, "{value}"), + } + } +} + +impl From for IpAddress { + fn from(value: Ipv4) -> Self { + Self::IPv4(value) + } +} + +impl From for IpAddress { + fn from(value: Ipv6) -> Self { + Self::IPv6(value) + } +} diff --git a/modules/network/src/fundamentals/kind.rs b/modules/network/src/fundamentals/kind.rs new file mode 100644 index 00000000..143b854f --- /dev/null +++ b/modules/network/src/fundamentals/kind.rs @@ -0,0 +1,8 @@ +#[repr(u8)] +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] +pub enum InterfaceKind { + #[default] + Unknown, + Ethernet, + WiFi, +} diff --git a/modules/network/src/fundamentals/mod.rs b/modules/network/src/fundamentals/mod.rs new file mode 100644 index 00000000..ace6afe8 --- /dev/null +++ b/modules/network/src/fundamentals/mod.rs @@ -0,0 +1,21 @@ +mod cidr; +mod dns; +mod duration; +mod endpoint; +mod ip; +mod kind; +mod port; +mod route; +mod udp; + +pub use cidr::*; +pub use dns::*; +pub use duration::*; +pub use endpoint::*; +pub use ip::*; +pub use kind::*; +pub use port::*; +pub use route::*; +pub use udp::*; + +pub type MacAddress = [u8; 6]; diff --git a/modules/network/src/fundamentals/port.rs b/modules/network/src/fundamentals/port.rs new file mode 100644 index 00000000..827909eb --- /dev/null +++ b/modules/network/src/fundamentals/port.rs @@ -0,0 +1,35 @@ +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Port(u16); + +impl Port { + pub const MINIMUM_USER: Self = Self(1025); + pub const MAXIMUM: Self = Self(u16::MAX); + + pub const DHCP_SERVER: Self = Self(67); + pub const DHCP_CLIENT: Self = Self(68); + + pub const fn new(value: u16) -> Self { + Self(value) + } + + pub const fn into_inner(self) -> u16 { + self.0 + } + + pub const fn from_inner(value: u16) -> Self { + Self(value) + } +} + +impl From for Port { + fn from(value: u16) -> Self { + Self::new(value) + } +} + +impl From for u16 { + fn from(value: Port) -> Self { + value.into_inner() + } +} diff --git a/modules/network/src/fundamentals/route.rs b/modules/network/src/fundamentals/route.rs new file mode 100644 index 00000000..ff17116f --- /dev/null +++ b/modules/network/src/fundamentals/route.rs @@ -0,0 +1,70 @@ +use core::time::Duration; + +use crate::{IpAddress, IpCidr}; + +#[repr(C)] +#[derive(Default, Clone, PartialEq, Eq)] +pub struct Route { + pub cidr: IpCidr, + pub via_router: IpAddress, + pub preferred_until: Option, + pub expires_at: Option, +} + +impl Route { + pub const fn new_default_ipv4(via_router: [u8; 4]) -> Self { + Self { + cidr: IpCidr::new_ipv4([0, 0, 0, 0], 0), + via_router: IpAddress::new_ipv4(via_router), + preferred_until: None, + expires_at: None, + } + } + + pub const fn new_default_ipv6(via_router: [u16; 8]) -> Self { + Self { + cidr: IpCidr::new_ipv6([0, 0, 0, 0, 0, 0, 0, 0], 0), + via_router: IpAddress::new_ipv6(via_router), + preferred_until: None, + expires_at: None, + } + } + + pub const fn from_smoltcp(route: smoltcp::iface::Route) -> Self { + let preferred_until = if let Some(dur) = route.preferred_until { + Some(Duration::from_micros(dur.micros() as u64)) + } else { + None + }; + + let expires_at = if let Some(dur) = route.expires_at { + Some(Duration::from_micros(dur.micros() as u64)) + } else { + None + }; + + Self { + cidr: IpCidr::from_smoltcp(&route.cidr), + via_router: IpAddress::from_smoltcp(&route.via_router), + preferred_until, + expires_at, + } + } + + pub fn into_smoltcp(&self) -> smoltcp::iface::Route { + let preferred_until = self + .preferred_until + .map(|dur| smoltcp::time::Instant::from_micros(dur.as_micros() as i64)); + + let expires_at = self + .expires_at + .map(|dur| smoltcp::time::Instant::from_micros(dur.as_micros() as i64)); + + smoltcp::iface::Route { + cidr: self.cidr.into_smoltcp(), + via_router: self.via_router.into_smoltcp(), + preferred_until, + expires_at, + } + } +} diff --git a/modules/network/src/fundamentals/udp.rs b/modules/network/src/fundamentals/udp.rs new file mode 100644 index 00000000..aa632a7b --- /dev/null +++ b/modules/network/src/fundamentals/udp.rs @@ -0,0 +1,58 @@ +use smoltcp::{phy::PacketMeta, socket::udp, wire}; + +use crate::{IpAddress, Port}; + +#[repr(C)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct UdpMetadata { + pub remote_address: IpAddress, + pub local_address: Option, + pub remote_port: Port, + pub meta: PacketMeta, +} + +impl UdpMetadata { + pub fn new( + remote_address: impl Into, + remote_port: impl Into, + local_address: Option, + meta: PacketMeta, + ) -> Self { + Self { + remote_address: remote_address.into(), + local_address, + remote_port: remote_port.into(), + meta, + } + } + + pub const fn from_smoltcp(value: &udp::UdpMetadata) -> Self { + let local_address = match value.local_address { + Some(addr) => Some(IpAddress::from_smoltcp(&addr)), + None => None, + }; + + Self { + remote_address: IpAddress::from_smoltcp(&value.endpoint.addr), + local_address, + remote_port: Port::from_inner(value.endpoint.port), + meta: value.meta, + } + } + + pub const fn to_smoltcp(&self) -> udp::UdpMetadata { + let local_address = match &self.local_address { + Some(addr) => Some(addr.into_smoltcp()), + None => None, + }; + + udp::UdpMetadata { + endpoint: wire::IpEndpoint::new( + self.remote_address.into_smoltcp(), + self.remote_port.into_inner(), + ), + local_address, + meta: self.meta, + } + } +} diff --git a/modules/network/src/ip.rs b/modules/network/src/ip.rs deleted file mode 100644 index 7e17f17e..00000000 --- a/modules/network/src/ip.rs +++ /dev/null @@ -1,171 +0,0 @@ -use core::fmt::Display; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[repr(transparent)] -pub struct IPv4([u8; 4]); - -impl IPv4 { - pub const LOCALHOST: Self = Self([127, 0, 0, 1]); - - pub const fn new(value: [u8; 4]) -> Self { - Self(value) - } - - pub const fn into_inner(self) -> [u8; 4] { - self.0 - } - - pub const fn from_inner(value: [u8; 4]) -> Self { - Self(value) - } -} - -impl TryFrom<&str> for IPv4 { - type Error = (); - - fn try_from(value: &str) -> Result { - let mut result = [0; 4]; - let mut index = 0; - - for part in value.split('.') { - if index >= 4 { - return Err(()); - } - let part = part.parse::().map_err(|_| ())?; - result[index] = part; - index += 1; - } - if index != 4 { - return Err(()); - } - - Ok(Self::new(result)) - } -} - -impl Display for IPv4 { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{}.{}.{}.{}", self.0[0], self.0[1], self.0[2], self.0[3]) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -#[repr(transparent)] -pub struct IPv6([u16; 8]); - -impl IPv6 { - pub const fn new(value: [u16; 8]) -> Self { - Self(value) - } - - pub const fn into_inner(self) -> [u16; 8] { - self.0 - } - - pub const fn from_inner(value: [u16; 8]) -> Self { - Self(value) - } -} - -impl TryFrom<&str> for IPv6 { - type Error = (); - - fn try_from(value: &str) -> Result { - let mut result = [0; 8]; - let mut index = 0; - - for part in value.split(':') { - if index >= result.len() { - return Err(()); - } - - let part = u16::from_str_radix(part, 16).map_err(|_| ())?; - result[index] = part; - index += 1; - } - if index != result.len() { - return Err(()); - } - - Ok(Self::new(result)) - } -} - -impl Display for IPv6 { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!( - f, - "{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}", - self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7] - ) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum IP { - IPv4(IPv4), - IPv6(IPv6), -} - -impl Display for IP { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - IP::IPv4(value) => write!(f, "{value}"), - IP::IPv6(value) => write!(f, "{value}"), - } - } -} - -impl From for IP { - fn from(value: IPv4) -> Self { - Self::IPv4(value) - } -} - -impl From for IP { - fn from(value: IPv6) -> Self { - Self::IPv6(value) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_ipv4_try_from() { - let ip = IPv4::try_from("0.0.0.0").unwrap(); - - assert_eq!(ip.0, [0, 0, 0, 0]); - - IPv4::try_from("1.2b.3.4").unwrap_err(); - - IPv4::try_from("1.2.3.4.5").unwrap_err(); - - IPv4::try_from("1.2.3").unwrap_err(); - - let ip = IPv4::try_from("4.3.2.1").unwrap(); - - assert_eq!(ip.0, [4, 3, 2, 1]); - } - - #[test] - fn test_ipv6_try_from() { - let ip = IPv6::try_from("0:0:0:0:0:0:0:0").unwrap(); - - assert_eq!(ip.0, [0; 8]); - - IPv6::try_from("0:0:0:0:0:0:0:0:0").unwrap_err(); - - IPv6::try_from("0:0:0:0:0:0:0").unwrap_err(); - - let ip = IPv6::try_from("1234:5678:9abc:def0:1234:5678:9abc:def0").unwrap(); - - assert_eq!( - ip.0, - [ - 0x1234, 0x5678, 0x9abc, 0xdef0, 0x1234, 0x5678, 0x9abc, 0xdef0 - ] - ); - } -} diff --git a/modules/network/src/lib.rs b/modules/network/src/lib.rs index 3e3a200d..e638bc18 100644 --- a/modules/network/src/lib.rs +++ b/modules/network/src/lib.rs @@ -2,14 +2,96 @@ extern crate alloc; +mod device; mod error; -mod ip; -mod protocol; -mod service; -mod traits; +mod fundamentals; +mod manager; +mod socket; +pub use device::*; pub use error::*; -pub use ip::*; -pub use protocol::*; -pub use service::*; -pub use traits::*; +pub use fundamentals::*; +pub use manager::*; +pub use socket::*; + +#[cfg(test)] +pub mod tests { + use file_system::AccessFlags; + use synchronization::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; + use virtual_file_system::{File, create_default_hierarchy}; + + use super::*; + + extern crate abi_definitions; + + drivers_std::memory::instantiate_global_allocator!(); + + pub(crate) async fn initialize() -> &'static crate::Manager { + static INITIALIZE_MUTEX: Mutex = Mutex::new(false); + + let mut initialized = INITIALIZE_MUTEX.lock().await; + + if *initialized { + return crate::get_instance(); + } + + *initialized = true; + + static RANDOM_DEVICE: drivers_shared::devices::RandomDevice = + drivers_shared::devices::RandomDevice; + static TIME_DEVICE: drivers_std::devices::TimeDevice = drivers_std::devices::TimeDevice; + + let task_manager = task::initialize(); + let task = task_manager.get_current_task_identifier().await; + + log::initialize(&drivers_std::log::Logger).unwrap(); + + let user_manager = users::initialize(); + + let time_manager = time::initialize(&TIME_DEVICE).unwrap(); + + let memory_device = file_system::MemoryDevice::<512>::new_static(10 * 1024 * 1024); + + let root_file_system = little_fs::FileSystem::get_or_format(memory_device, 512).unwrap(); + + let virtual_file_system = virtual_file_system::initialize( + task_manager, + user_manager, + time_manager, + root_file_system, + ) + .unwrap(); + + create_default_hierarchy(virtual_file_system, task) + .await + .unwrap(); + + let network_manager = crate::initialize(task_manager, virtual_file_system, &RANDOM_DEVICE); + + let (device, controler_device) = crate::create_loopback_device(); + + let spawner = drivers_std::executor::new_thread_executor().await; + + network_manager + .mount_interface(task, "loopback0", device, controler_device, Some(spawner)) + .await + .unwrap(); + + let mut file = File::open( + virtual_file_system, + task, + "/devices/network/loopback0", + AccessFlags::Write.into(), + ) + .await + .unwrap(); + + file.control(ADD_IP_ADDRESS, &IpCidr::new_ipv4([127, 0, 0, 1], 8)) + .await + .unwrap(); + + file.close(virtual_file_system).await.unwrap(); + + network_manager + } +} diff --git a/modules/network/src/manager/context.rs b/modules/network/src/manager/context.rs new file mode 100644 index 00000000..34bb8dca --- /dev/null +++ b/modules/network/src/manager/context.rs @@ -0,0 +1,63 @@ +use core::task::{Context, Poll}; + +use crate::manager::stack::Stack; +use smoltcp::iface::SocketHandle; + +pub struct SocketContext { + pub handle: SocketHandle, + pub stack: Stack, + pub closed: bool, +} + +impl SocketContext { + pub async fn with(&self, f: F) -> R + where + F: FnOnce(&S) -> R, + S: smoltcp::socket::AnySocket<'static>, + { + self.stack + .with(|stack_inner| { + let socket = stack_inner.sockets.get::(self.handle); + f(socket) + }) + .await + } + + pub async fn with_mutable(&self, f: F) -> R + where + F: FnOnce(&mut S) -> R, + S: smoltcp::socket::AnySocket<'static>, + { + self.stack + .with_mutable(|stack_inner| { + let socket = stack_inner.sockets.get_mut::(self.handle); + f(socket) + }) + .await + } + + pub fn poll_with(&self, context: &mut Context<'_>, f: F) -> Poll + where + F: FnOnce(&S, &mut Context<'_>) -> Poll, + S: smoltcp::socket::AnySocket<'static>, + { + self.stack.poll_with(context, |stack_inner, context| { + let socket = stack_inner.sockets.get::(self.handle); + + f(socket, context) + }) + } + + pub fn poll_with_mutable(&self, context: &mut core::task::Context<'_>, f: F) -> Poll + where + F: FnOnce(&mut S, &mut Context<'_>) -> Poll, + S: smoltcp::socket::AnySocket<'static>, + { + self.stack + .poll_with_mutable(context, |stack_inner, context| { + let socket = stack_inner.sockets.get_mut::(self.handle); + + f(socket, context) + }) + } +} diff --git a/modules/network/src/manager/device.rs b/modules/network/src/manager/device.rs new file mode 100644 index 00000000..4d0a4155 --- /dev/null +++ b/modules/network/src/manager/device.rs @@ -0,0 +1,163 @@ +use crate::{ + ADD_DNS_SERVER, ADD_IP_ADDRESS, ADD_ROUTE, GET_DHCP_STATE, GET_DNS_SERVER, + GET_DNS_SERVER_COUNT, GET_HARDWARE_ADDRESS, GET_IP_ADDRESS, GET_IP_ADDRESS_COUNT, + GET_MAXIMUM_BURST_SIZE, GET_MAXIMUM_TRANSMISSION_UNIT, GET_ROUTE, GET_ROUTE_COUNT, GET_STATE, + IS_LINK_UP, IpAddress, IpCidr, MacAddress, REMOVE_DNS_SERVER, REMOVE_IP_ADDRESS, REMOVE_ROUTE, + Route, SET_DHCP_STATE, SET_HARDWARE_ADDRESS, SET_STATE, + manager::stack::{Stack, StackInner}, +}; +use file_system::{ + ControlCommand, ControlCommandIdentifier, DirectBaseOperations, DirectCharacterDevice, Error, + MountOperations, Result, +}; +use shared::AnyByLayout; + +#[repr(transparent)] +pub struct NetworkDevice { + stack: Stack, +} + +impl NetworkDevice { + pub fn with R>(&self, f: F) -> Result { + let stack = self.stack.try_lock().ok_or(Error::RessourceBusy)?; + + Ok(f(&stack)) + } + + pub fn with_mut R>(&self, f: F) -> Result { + let mut stack = self.stack.try_lock().ok_or(Error::RessourceBusy)?; + + Ok(f(&mut stack)) + } +} + +impl NetworkDevice { + pub fn new(stack: Stack) -> NetworkDevice { + NetworkDevice { stack } + } +} + +impl DirectBaseOperations for NetworkDevice { + fn read(&self, _: &mut [u8], _: file_system::Size) -> file_system::Result { + Err(Error::UnsupportedOperation) + } + + fn write(&self, _: &[u8], _: file_system::Size) -> file_system::Result { + Err(Error::UnsupportedOperation) + } + + fn control( + &self, + command: ControlCommandIdentifier, + input: &AnyByLayout, + output: &mut AnyByLayout, + ) -> Result<()> { + match command { + SET_STATE::IDENTIFIER => { + let state: &bool = SET_STATE::cast_input(input)?; + self.with_mut(|s| s.set_state(*state))?; + } + GET_STATE::IDENTIFIER => { + let state: &mut bool = GET_STATE::cast_output(output)?; + *state = self.with(|s| s.get_state())?; + } + IS_LINK_UP::IDENTIFIER => { + let is_up: &mut bool = IS_LINK_UP::cast_output(output)?; + *is_up = self.with(|s| s.is_link_up())?; + } + GET_HARDWARE_ADDRESS::IDENTIFIER => { + let hardware_address: &mut MacAddress = GET_HARDWARE_ADDRESS::cast_output(output)?; + let address = self.with(|s| s.interface.hardware_addr())?; + hardware_address.copy_from_slice(address.as_bytes()); + } + SET_HARDWARE_ADDRESS::IDENTIFIER => { + let hardware_address: &MacAddress = SET_HARDWARE_ADDRESS::cast_input(input)?; + self.with_mut(|s| s.set_hardware_address(hardware_address))?; + } + GET_MAXIMUM_TRANSMISSION_UNIT::IDENTIFIER => { + let mtu: &mut usize = GET_MAXIMUM_TRANSMISSION_UNIT::cast_output(output)?; + *mtu = self.with(|s| s.get_maximum_transmission_unit())?; + } + GET_MAXIMUM_BURST_SIZE::IDENTIFIER => { + let maximum_burst_size: &mut Option = + GET_MAXIMUM_BURST_SIZE::cast_output(output)?; + *maximum_burst_size = self.with(|s| s.get_maximum_burst_size())?; + } + GET_IP_ADDRESS_COUNT::IDENTIFIER => { + let ip_address_count: &mut usize = GET_IP_ADDRESS_COUNT::cast_output(output)?; + *ip_address_count = self.with(|s| s.get_ip_addresses_count())?; + } + GET_IP_ADDRESS::IDENTIFIER => { + let (input, output) = GET_IP_ADDRESS::cast(input, output)?; + *output = self + .with(|s| s.get_ip_address(*input))? + .ok_or(Error::InvalidParameter)?; + } + ADD_IP_ADDRESS::IDENTIFIER => { + let indexed: &IpCidr = ADD_IP_ADDRESS::cast_input(input)?; + self.with_mut(|s| s.add_ip_address(indexed.clone()))? + .map_err(|_| Error::InternalError)?; + } + REMOVE_IP_ADDRESS::IDENTIFIER => { + let ip_address_index: &usize = REMOVE_IP_ADDRESS::cast_input(input)?; + self.with_mut(|s| s.remove_ip_address(*ip_address_index))?; + } + GET_ROUTE_COUNT::IDENTIFIER => { + let route_count: &mut usize = GET_ROUTE_COUNT::cast_output(output)?; + *route_count = self.with_mut(|s| s.get_route_count())?; + } + GET_ROUTE::IDENTIFIER => { + let (input, output) = GET_ROUTE::cast(input, output)?; + *output = self + .with_mut(|s| s.get_route(*input))? + .ok_or(Error::InvalidParameter)?; + } + ADD_ROUTE::IDENTIFIER => { + let route_index: &Route = ADD_ROUTE::cast_input(input)?; + self.with_mut(|s| s.add_route(route_index.clone()))? + .map_err(|_| Error::InternalError)?; + } + REMOVE_ROUTE::IDENTIFIER => { + let route_index: &usize = REMOVE_ROUTE::cast_input(input)?; + self.with_mut(|s| s.remove_route(*route_index))?; + } + GET_DNS_SERVER_COUNT::IDENTIFIER => { + let dns_server_count: &mut usize = GET_DNS_SERVER_COUNT::cast_output(output)?; + *dns_server_count = self.with_mut(|s| s.get_dns_servers_count())?; + } + GET_DNS_SERVER::IDENTIFIER => { + let (input, output) = GET_DNS_SERVER::cast(input, output)?; + + *output = self + .with_mut(|s| s.get_dns_server(*input))? + .ok_or(Error::InvalidParameter)?; + } + ADD_DNS_SERVER::IDENTIFIER => { + let dns_server: &IpAddress = ADD_DNS_SERVER::cast_input(input)?; + self.with_mut(|s| s.add_dns_server(dns_server.clone()))? + .map_err(|_| Error::InternalError)?; + } + REMOVE_DNS_SERVER::IDENTIFIER => { + let index: &usize = REMOVE_DNS_SERVER::cast_input(input)?; + self.with_mut(|s| s.remove_dns_server(*index))?; + } + SET_DHCP_STATE::IDENTIFIER => { + let dhcp_enabled: &bool = SET_DHCP_STATE::cast_input(input)?; + self.with_mut(|s| s.set_dhcp_state(*dhcp_enabled))?; + } + GET_DHCP_STATE::IDENTIFIER => { + let dhcp_enabled: &mut bool = GET_DHCP_STATE::cast_output(output)?; + *dhcp_enabled = self.with(|s| s.get_dhcp_state())?; + } + command => { + self.with(|s| s.controller.control(command, input, output))??; + } + } + + Ok(()) + } +} + +impl MountOperations for NetworkDevice {} + +impl DirectCharacterDevice for NetworkDevice {} diff --git a/modules/network/src/manager/mod.rs b/modules/network/src/manager/mod.rs new file mode 100644 index 00000000..1034e245 --- /dev/null +++ b/modules/network/src/manager/mod.rs @@ -0,0 +1,320 @@ +mod context; +mod device; +mod runner; +mod stack; + +use alloc::{vec, vec::Vec}; +pub use context::*; +use file_system::{DirectCharacterDevice, Path}; +pub use runner::*; +use smoltcp::{ + phy::Device, + socket::{dns, icmp, tcp, udp}, +}; +use synchronization::once_lock::OnceLock; +use synchronization::{ + Arc, blocking_mutex::raw::CriticalSectionRawMutex, rwlock::RwLock, signal::Signal, +}; +use task::{SpawnerIdentifier, TaskIdentifier}; +use virtual_file_system::VirtualFileSystem; + +use crate::{ + DnsSocket, Error, IcmpSocket, Result, TcpSocket, UdpSocket, + manager::{ + device::NetworkDevice, + stack::{Stack, StackInner}, + }, +}; + +static MANAGER_INSTANCE: OnceLock = OnceLock::new(); + +pub fn get_instance() -> &'static Manager { + MANAGER_INSTANCE + .try_get() + .expect("Manager is not initialized") +} + +pub fn initialize( + _task_manager: &'static task::Manager, + _virtual_file_system: &'static VirtualFileSystem, + random_device: &'static dyn DirectCharacterDevice, +) -> &'static Manager { + MANAGER_INSTANCE.get_or_init(|| Manager::new(random_device)) +} + +pub fn get_smoltcp_time() -> smoltcp::time::Instant { + let time_manager = time::get_instance(); + let current_time = time_manager + .get_current_time() + .expect("Failed to get current time"); + + smoltcp::time::Instant::from_millis(current_time.as_millis() as i64) +} + +type StackList = Vec; + +pub struct Manager { + pub(crate) random_device: &'static dyn DirectCharacterDevice, + pub(crate) stacks: RwLock, +} + +impl Manager { + pub fn new(random_device: &'static dyn DirectCharacterDevice) -> Self { + Manager { + random_device, + stacks: RwLock::new(Vec::new()), + } + } + + fn generate_seed(&self) -> Result { + let mut buffer = [0u8; 8]; + self.random_device + .read(&mut buffer, 0) + .map_err(Error::FailedToGenerateSeed)?; + Ok(u64::from_le_bytes(buffer)) + } + + pub(crate) async fn find_first_available_stack(stacks: &StackList) -> Option { + for stack in stacks { + let available = stack.with(|s| s.is_available()).await; + + if available { + return Some(stack.clone()); + } + } + + None + } + + pub(crate) async fn find_stack(stacks: &StackList, name: &str) -> Option { + for stack in stacks { + let stack_name = stack.with(|s| s.name.clone()).await; + + if stack_name.as_str() == name { + return Some(stack.clone()); + } + } + + None + } + + pub async fn mount_interface( + &self, + task: TaskIdentifier, + name: &str, + mut device: impl Device + 'static, + controller_device: impl DirectCharacterDevice + 'static, + spawner: Option, + ) -> Result<()> { + let mut stacks = self.stacks.write().await; + + if Self::find_stack(&stacks, name).await.is_some() { + return Err(Error::DuplicateIdentifier); + } + + let random_seed = self.generate_seed()?; + let now = get_smoltcp_time(); + + let stack_inner = StackInner::new(name, &mut device, controller_device, random_seed, now); + + // Create a wake signal for runner/stack communication + let wake_signal: WakeSignal = Arc::new(Signal::new()); + + let stack = Stack::new(stack_inner, wake_signal.clone()); + + let mut runner = StackRunner::new(stack.clone(), device, wake_signal); + + let task_manager = task::get_instance(); + + task_manager + .spawn( + task, + "Network Interface Runner", + spawner, + move |_| async move { + runner.run().await; + }, + ) + .await + .map_err(Error::FailedToSpawnNetworkTask)?; + + let path = Path::NETWORK_DEVICES + .join(Path::from_str(name)) + .ok_or(Error::InvalidIdentifier)?; + + let device = NetworkDevice::new(stack.clone()); + + let virtual_file_system = virtual_file_system::get_instance(); + + match virtual_file_system + .create_directory(task, &Path::NETWORK_DEVICES) + .await + { + Ok(_) => {} + Err(virtual_file_system::Error::AlreadyExists) => {} + Err(e) => return Err(Error::FailedToMountDevice(e)), + }; + + match virtual_file_system.remove(task, &path).await { + Ok(_) => {} + Err(virtual_file_system::Error::FileSystem(file_system::Error::NotFound)) => {} + Err(e) => return Err(Error::FailedToMountDevice(e)), + }; + + virtual_file_system + .mount_character_device(task, path, device) + .await + .map_err(Error::FailedToMountDevice)?; + + stacks.push(stack); + + Ok(()) + } + + pub async fn new_dns_socket(&self, interface_name: Option<&str>) -> Result { + let stacks = self.stacks.read().await; + + let stack = if let Some(name) = interface_name { + Self::find_stack(&stacks, name) + .await + .ok_or(Error::NotFound)? + } else { + Self::find_first_available_stack(&stacks) + .await + .ok_or(Error::NotFound)? + }; + + let handle = stack + .with_mutable(|s| { + let socket = dns::Socket::new(&s.dns_servers, vec![]); + s.add_socket(socket) + }) + .await; + + let context = SocketContext { + handle, + stack: stack.clone(), + closed: false, + }; + let socket = DnsSocket::new(context); + + Ok(socket) + } + + pub async fn new_tcp_socket( + &self, + transmit_buffer_size: usize, + receive_buffer_size: usize, + interface_name: Option<&str>, + ) -> Result { + let stacks = self.stacks.read().await; + + let stack = if let Some(name) = interface_name { + Self::find_stack(&stacks, name) + .await + .ok_or(Error::NotFound)? + } else { + Self::find_first_available_stack(&stacks) + .await + .ok_or(Error::NotFound)? + }; + + let send_buffer = tcp::SocketBuffer::new(vec![0u8; transmit_buffer_size]); + let receive_buffer = tcp::SocketBuffer::new(vec![0u8; receive_buffer_size]); + + let socket = tcp::Socket::new(receive_buffer, send_buffer); + let handle = stack.with_mutable(|s| s.add_socket(socket)).await; + + let context = SocketContext { + handle, + stack: stack.clone(), + closed: false, + }; + + Ok(TcpSocket::new(context)) + } + + pub async fn new_udp_socket( + &self, + transmit_buffer_size: usize, + receive_buffer_size: usize, + receive_meta_buffer_size: usize, + transmit_meta_buffer_size: usize, + interface_name: Option<&str>, + ) -> Result { + let stacks = self.stacks.read().await; + + let stack = if let Some(name) = interface_name { + Self::find_stack(&stacks, name) + .await + .ok_or(Error::NotFound)? + } else { + Self::find_first_available_stack(&stacks) + .await + .ok_or(Error::NotFound)? + }; + + let receive_meta_buffer = udp::PacketBuffer::new( + vec![udp::PacketMetadata::EMPTY; receive_meta_buffer_size], + vec![0u8; receive_buffer_size], + ); + let transmit_meta_buffer = udp::PacketBuffer::new( + vec![udp::PacketMetadata::EMPTY; transmit_meta_buffer_size], + vec![0u8; transmit_buffer_size], + ); + + let socket = udp::Socket::new(receive_meta_buffer, transmit_meta_buffer); + let handle = stack.with_mutable(|s| s.add_socket(socket)).await; + + let context = SocketContext { + handle, + stack: stack.clone(), + closed: false, + }; + + Ok(UdpSocket::new(context)) + } + + pub async fn new_icmp_socket( + &self, + receive_buffer_size: usize, + transmit_buffer_size: usize, + receive_meta_buffer_size: usize, + transmit_meta_buffer_size: usize, + interface_name: Option<&str>, + ) -> Result { + let stacks = self.stacks.read().await; + + let stack = if let Some(name) = interface_name { + Self::find_stack(&stacks, name) + .await + .ok_or(Error::NotFound)? + } else { + Self::find_first_available_stack(&stacks) + .await + .ok_or(Error::NotFound)? + }; + + let receive_buffer = icmp::PacketBuffer::new( + vec![icmp::PacketMetadata::EMPTY; receive_meta_buffer_size], + vec![0u8; receive_buffer_size], + ); + let transmit_buffer = icmp::PacketBuffer::new( + vec![icmp::PacketMetadata::EMPTY; transmit_meta_buffer_size], + vec![0u8; transmit_buffer_size], + ); + + let socket = icmp::Socket::new(receive_buffer, transmit_buffer); + let handle = stack.with_mutable(|s| s.add_socket(socket)).await; + + let context = SocketContext { + handle, + stack: stack.clone(), + closed: false, + }; + + let socket = IcmpSocket::new(context); + + Ok(socket) + } +} diff --git a/modules/network/src/manager/runner.rs b/modules/network/src/manager/runner.rs new file mode 100644 index 00000000..bc216eb8 --- /dev/null +++ b/modules/network/src/manager/runner.rs @@ -0,0 +1,53 @@ +use crate::manager::stack::Stack; +use core::time::Duration; +use embassy_futures::select::select; +use smoltcp::phy::Device; +use synchronization::{Arc, blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal}; + +/// A signal used to wake the runner when socket activity occurs. +pub type WakeSignal = Arc>; + +pub struct StackRunner { + stack: Stack, + device: T, + wake_signal: WakeSignal, +} + +impl StackRunner +where + T: Device, +{ + pub fn new(stack: Stack, device: T, wake_signal: WakeSignal) -> Self { + Self { + stack, + device, + wake_signal, + } + } + + pub async fn run(&mut self) -> ! { + loop { + let next_poll_in = self + .stack + .with_mutable_no_wake(|stack_inner| { + if stack_inner.enabled { + stack_inner.poll(&mut self.device) + } else { + None + } + }) + .await; + + let sleep_duration = match next_poll_in { + Some(d) if d.is_zero() => { + embassy_futures::yield_now().await; + continue; + } + Some(d) => d, + None => Duration::from_millis(200), + }; + + select(task::sleep(sleep_duration), self.wake_signal.wait()).await; + } + } +} diff --git a/modules/network/src/manager/stack.rs b/modules/network/src/manager/stack.rs new file mode 100644 index 00000000..8e01b59c --- /dev/null +++ b/modules/network/src/manager/stack.rs @@ -0,0 +1,383 @@ +use crate::{ + Error, IpAddress, IpCidr, Ipv4, Ipv6, MacAddress, Port, Result, Route, WakeSignal, + get_smoltcp_time, +}; +use alloc::{boxed::Box, vec, vec::Vec}; +use core::{ + task::{Context, Poll}, + time::Duration, +}; +use file_system::DirectCharacterDevice; +use shared::poll_pin_ready; +use smol_str::SmolStr; +use smoltcp::{ + config::{DNS_MAX_SERVER_COUNT, IFACE_MAX_ADDR_COUNT}, + iface::{self, SocketSet}, + phy::{Device, Medium}, + socket::{AnySocket, Socket, dhcpv4}, + wire::{self, EthernetAddress}, +}; +use synchronization::{Arc, blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; + +pub struct StackInner { + pub name: SmolStr, + pub is_up: bool, + pub enabled: bool, + pub interface: smoltcp::iface::Interface, + pub controller: Box, + pub sockets: smoltcp::iface::SocketSet<'static>, + pub dhcp_socket: Option, + pub dns_servers: Vec, + pub maximum_transmission_unit: usize, + pub maximum_burst_size: Option, + pub next_local_port: Port, +} + +#[derive(Clone)] +pub struct Stack { + inner: Arc>, + wake_signal: WakeSignal, +} + +impl Stack { + pub fn new(inner: StackInner, wake_signal: WakeSignal) -> Self { + Stack { + inner: Arc::new(Mutex::new(inner)), + wake_signal, + } + } + + pub fn wake_up(&self) -> &WakeSignal { + &self.wake_signal + } + + /// Try to lock the inner stack without blocking. Returns None if the lock is held. + pub fn try_lock( + &self, + ) -> Option> { + self.inner.try_lock().ok() + } + + /// Lock the inner stack asynchronously. + pub async fn lock( + &self, + ) -> synchronization::mutex::MutexGuard<'_, CriticalSectionRawMutex, StackInner> { + self.inner.lock().await + } + + /// Signal the runner to wake up (call after modifying the stack outside of with_mutable). + pub fn wake_runner(&self) { + self.wake_signal.signal(()); + } + + pub async fn with R>(&self, f: F) -> R { + let stack = self.inner.lock().await; + f(&stack) + } + + pub async fn with_mutable R>(&self, f: F) -> R { + let mut stack = self.inner.lock().await; + let r = f(&mut stack); + // Wake the runner via signal + self.wake_signal.signal(()); + r + } + + pub async fn with_mutable_no_wake R>(&self, f: F) -> R { + let mut stack = self.inner.lock().await; + f(&mut stack) + } + + pub fn poll_with) -> Poll>( + &self, + context: &mut Context<'_>, + f: F, + ) -> Poll { + let stack = poll_pin_ready!(self.inner.lock(), context); + f(&stack, context) + } + + fn poll_with_mutable_impl) -> Poll>( + &self, + context: &mut core::task::Context<'_>, + f: F, + wake: bool, + ) -> Poll { + let mut stack = poll_pin_ready!(self.inner.lock(), context); + + let r = f(&mut stack, context); + if wake { + // Wake the runner via signal + self.wake_signal.signal(()); + } + r + } + + pub fn poll_with_mutable) -> Poll>( + &self, + context: &mut core::task::Context<'_>, + f: F, + ) -> Poll { + self.poll_with_mutable_impl(context, f, true) + } +} + +impl StackInner { + pub fn new( + name: impl AsRef, + device: &mut (impl Device + 'static), + controller_device: impl DirectCharacterDevice + 'static, + random_seed: u64, + now: smoltcp::time::Instant, + ) -> Self { + let capabilities = device.capabilities(); + + let mut config = match capabilities.medium { + Medium::Ethernet => { + iface::Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) + } + Medium::Ip => iface::Config::new(smoltcp::wire::HardwareAddress::Ip), + Medium::Ieee802154 => todo!(), + }; + config.random_seed = random_seed; + + let interface = smoltcp::iface::Interface::new(config, device, now); + + let sockets = SocketSet::new(vec![]); + + let next_local_port = (random_seed + % (Port::MAXIMUM.into_inner() - Port::MINIMUM_USER.into_inner()) as u64) + as u16 + + Port::MINIMUM_USER.into_inner(); + + StackInner { + name: name.as_ref().into(), + is_up: true, + enabled: true, + interface, + controller: Box::new(controller_device), + sockets, + dhcp_socket: None, + dns_servers: Vec::with_capacity(DNS_MAX_SERVER_COUNT), + maximum_transmission_unit: capabilities.max_transmission_unit, + maximum_burst_size: capabilities.max_burst_size, + next_local_port: Port::from_inner(next_local_port), + } + } + + pub fn is_available(&self) -> bool { + self.enabled && self.is_up + } + + pub fn is_link_up(&self) -> bool { + self.is_up + } + + pub fn get_state(&self) -> bool { + self.enabled + } + + pub fn set_state(&mut self, enabled: bool) { + self.enabled = enabled; + } + + pub fn get_route(&mut self, index: usize) -> Option { + let mut route = None; + + self.interface.routes_mut().update(|v| { + if let Some(r) = v.get(index) { + route = Some(*r); + } + }); + + route.map(Route::from_smoltcp) + } + + pub fn get_route_count(&mut self) -> usize { + let mut count = 0; + + self.interface.routes_mut().update(|v| count = v.len()); + + count + } + + pub fn add_route(&mut self, route: Route) -> Result<()> { + let mut result = Ok(()); + + self.interface + .routes_mut() + .update(|v| result = v.push(route.into_smoltcp()).map_err(|_| Error::NoFreeSlot)); + + result + } + + pub fn remove_route(&mut self, index: usize) -> Option { + let mut route = None; + + self.interface.routes_mut().update(|v| { + if index < v.len() { + route = Some(v.remove(index)); + } + }); + + route.map(Route::from_smoltcp) + } + + pub fn get_dns_servers_count(&self) -> usize { + self.dns_servers.len() + } + + pub fn get_dns_server(&self, index: usize) -> Option { + self.dns_servers.get(index).map(IpAddress::from_smoltcp) + } + + pub fn add_dns_server(&mut self, server: IpAddress) -> Result<()> { + if self.dns_servers.len() >= DNS_MAX_SERVER_COUNT { + return Err(Error::NoFreeSlot); + } + + self.dns_servers.push(server.into_smoltcp()); + Ok(()) + } + + pub fn remove_dns_server(&mut self, index: usize) -> Option { + if index >= self.dns_servers.len() { + return None; + } + + let server = self.dns_servers.remove(index); + Some(IpAddress::from_smoltcp(&server)) + } + + pub fn get_dns_servers(&self) -> &[wire::IpAddress] { + &self.dns_servers + } + + pub fn set_dhcp_state(&mut self, enabled: bool) { + if enabled { + let dhcp_socket = dhcpv4::Socket::new(); + let handle = self.sockets.add(dhcp_socket); + self.dhcp_socket = Some(handle); + } else { + self.dhcp_socket.take(); + } + } + + pub fn get_dhcp_state(&self) -> bool { + self.dhcp_socket.is_some() + } + + pub fn get_ip_addresses_count(&self) -> usize { + self.interface.ip_addrs().len() + } + + pub fn get_ip_address(&self, index: usize) -> Option { + self.interface + .ip_addrs() + .get(index) + .map(IpCidr::from_smoltcp) + } + + pub fn add_ip_address(&mut self, cidr: IpCidr) -> Result<()> { + if self.get_ip_addresses_count() >= IFACE_MAX_ADDR_COUNT { + return Err(Error::NoFreeSlot); + } + + let mut result = Ok(()); + + self.interface.update_ip_addrs(|addrs| { + result = addrs + .push(cidr.into_smoltcp()) + .map_err(|_| Error::NoFreeSlot); + }); + + result + } + + pub fn remove_ip_address(&mut self, index: usize) -> Option { + let mut cidr = None; + + self.interface.update_ip_addrs(|addrs| { + if index < addrs.len() { + cidr = Some(addrs.remove(index)); + } + }); + + cidr.as_ref().map(IpCidr::from_smoltcp) + } + + pub fn set_hardware_address(&mut self, address: &MacAddress) { + self.interface + .set_hardware_addr(smoltcp::wire::HardwareAddress::Ethernet( + smoltcp::wire::EthernetAddress(*address), + )); + } + + pub fn get_hardware_address(&self) -> Option { + match self.interface.hardware_addr() { + smoltcp::wire::HardwareAddress::Ethernet(addr) => Some(MacAddress::from(addr.0)), + _ => None, + } + } + + pub fn get_maximum_transmission_unit(&self) -> usize { + self.maximum_transmission_unit + } + + pub fn get_maximum_burst_size(&self) -> Option { + self.maximum_burst_size + } + + pub fn add_socket(&mut self, socket: impl AnySocket<'static>) -> smoltcp::iface::SocketHandle { + self.sockets.add(socket) + } + + pub fn remove_socket<'a>(&'a mut self, handle: smoltcp::iface::SocketHandle) -> Socket<'a> { + self.sockets.remove(handle) + } + + pub fn get_source_ip_v6_address(&mut self, remote: Ipv6) -> Ipv6 { + let ip = self + .interface + .get_source_address_ipv6(&remote.into_smoltcp()); + + Ipv6::from_smoltcp(&ip) + } + + pub fn get_source_ip_v4_address(&mut self, remote: Ipv4) -> Option { + self.interface + .get_source_address_ipv4(&remote.into_smoltcp()) + .map(|ip| Ipv4::from_smoltcp(&ip)) + } + + pub fn poll(&mut self, device: &mut impl Device) -> Option { + let now = get_smoltcp_time(); + + self.interface.poll(now, device, &mut self.sockets); + + let poll_at = self.interface.poll_at(now, &self.sockets); + + poll_at + .map(|instant| instant - now) + .map(|smoltcp_duration| Duration::from_micros(smoltcp_duration.total_micros())) + } + + pub fn get_socket>( + &mut self, + handle: smoltcp::iface::SocketHandle, + ) -> &mut S { + self.sockets.get_mut::(handle) + } + + pub fn get_next_port(&mut self) -> Port { + let port = self.next_local_port; + + self.next_local_port = if port.into_inner() == Port::MAXIMUM.into_inner() { + Port::MINIMUM_USER + } else { + Port::from_inner(port.into_inner() + 1) + }; + + port + } +} diff --git a/modules/network/src/protocol.rs b/modules/network/src/protocol.rs deleted file mode 100644 index 7e7c8237..00000000 --- a/modules/network/src/protocol.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub enum Protocol { - TCP, - UDP, - ICMP, - Local, -} diff --git a/modules/network/src/service.rs b/modules/network/src/service.rs deleted file mode 100644 index 75cecb61..00000000 --- a/modules/network/src/service.rs +++ /dev/null @@ -1,27 +0,0 @@ -use core::fmt::Display; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[repr(transparent)] -pub struct Port(u16); - -impl Port { - pub const ANY: Self = Self(0); - - pub const fn new(value: u16) -> Self { - Self(value) - } - - pub const fn into_inner(self) -> u16 { - self.0 - } - - pub const fn from_inner(value: u16) -> Self { - Self(value) - } -} - -impl Display for Port { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{}", self.0) - } -} diff --git a/modules/network/src/socket/dns.rs b/modules/network/src/socket/dns.rs new file mode 100644 index 00000000..39fe4834 --- /dev/null +++ b/modules/network/src/socket/dns.rs @@ -0,0 +1,139 @@ +use core::{ + future::poll_fn, + task::{Context, Poll}, +}; + +use alloc::{vec, vec::Vec}; +use embassy_futures::block_on; +use smoltcp::{socket::dns, wire::DnsQueryType}; + +use crate::{DnsQueryKind, IpAddress, Result, SocketContext}; + +pub struct DnsSocket { + context: SocketContext, +} + +impl DnsSocket { + pub fn new(context: SocketContext) -> Self { + Self { context } + } + + fn poll_with_mutable(&self, context: &mut Context<'_>, f: F) -> Poll + where + F: FnOnce(&mut dns::Socket<'static>, &mut Context<'_>) -> Poll, + { + self.context.poll_with_mutable(context, f) + } + + pub async fn update_servers(&self) -> Result<()> { + self.context + .stack + .with_mutable(|s| { + let dns_servers = s.get_dns_servers().to_vec(); + let socket = s.sockets.get_mut::(self.context.handle); + socket.update_servers(&dns_servers); + }) + .await; + + Ok(()) + } + + pub async fn resolve_for_kind(&self, host: &str, kind: DnsQueryType) -> Result> { + if let Ok(host) = IpAddress::try_from(host) { + return Ok(vec![host]); + } + + let query = self + .context + .stack + .with_mutable(|s| { + let socket = s.sockets.get_mut::(self.context.handle); + + socket.start_query(s.interface.context(), host, kind) + }) + .await?; + + self.context.stack.wake_up(); + + poll_fn(|cx| { + self.poll_with_mutable(cx, |socket, cx| match socket.get_query_result(query) { + Err(dns::GetQueryResultError::Pending) => { + socket.register_query_waker(query, cx.waker()); + Poll::Pending + } + Err(e) => Poll::Ready(Err(e.into())), + Ok(ip_addresses) => { + let ip_addresses = ip_addresses + .into_iter() + .map(|a| IpAddress::from_smoltcp(&a)) + .collect(); + + Poll::Ready(Ok(ip_addresses)) + } + }) + }) + .await + } + + pub async fn resolve(&self, host: &str, kind: DnsQueryKind) -> Result> { + let mut results = Vec::new(); + + if kind.contains(DnsQueryKind::A) { + let mut a_results = self.resolve_for_kind(host, DnsQueryType::A).await?; + results.append(&mut a_results); + } + + if kind.contains(DnsQueryKind::Aaaa) { + let mut aaaa_results = self.resolve_for_kind(host, DnsQueryType::Aaaa).await?; + results.append(&mut aaaa_results); + } + + if kind.contains(DnsQueryKind::Cname) { + let mut cname_results = self.resolve_for_kind(host, DnsQueryType::Cname).await?; + results.append(&mut cname_results); + } + + if kind.contains(DnsQueryKind::Ns) { + let mut ns_results = self.resolve_for_kind(host, DnsQueryType::Ns).await?; + results.append(&mut ns_results); + } + + if kind.contains(DnsQueryKind::Soa) { + let mut soa_results = self.resolve_for_kind(host, DnsQueryType::Soa).await?; + results.append(&mut soa_results); + } + + Ok(results) + } + + pub async fn close(mut self) -> Result<()> { + if self.context.closed { + return Ok(()); + } + + self.context + .stack + .with_mutable(|s| { + let _ = s.remove_socket(self.context.handle); + }) + .await; + + self.context.closed = true; + + Ok(()) + } +} + +impl Drop for DnsSocket { + fn drop(&mut self) { + if self.context.closed { + return; + } + + log::warning!("DNS socket dropped without being closed. Forcing closure..."); + + block_on(self.context.stack.with_mutable(|s| { + let _ = s.remove_socket(self.context.handle); + })); + } +} diff --git a/modules/network/src/socket/icmp.rs b/modules/network/src/socket/icmp.rs new file mode 100644 index 00000000..9444ec44 --- /dev/null +++ b/modules/network/src/socket/icmp.rs @@ -0,0 +1,380 @@ +use crate::{Duration, Error, IpAddress, Port, Result, SocketContext}; +use alloc::vec; +use core::{ + future::poll_fn, + task::{Context, Poll}, +}; +use smoltcp::{socket::icmp, wire}; + +#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone)] +pub enum IcmpEndpoint { + #[default] + Unspecified, + Identifier(u16), + Udp((Option, Port)), +} + +impl IcmpEndpoint { + pub const fn into_smoltcp(&self) -> icmp::Endpoint { + match self { + IcmpEndpoint::Unspecified => icmp::Endpoint::Unspecified, + IcmpEndpoint::Identifier(id) => icmp::Endpoint::Ident(*id), + IcmpEndpoint::Udp((addr_opt, port)) => { + let addr = match addr_opt { + Some(a) => Some(a.into_smoltcp()), + None => None, + }; + + let endpoint = wire::IpListenEndpoint { + addr, + port: port.into_inner(), + }; + + icmp::Endpoint::Udp(endpoint) + } + } + } +} + +pub struct IcmpSocket { + context: SocketContext, +} + +impl IcmpSocket { + pub(crate) fn new(context: SocketContext) -> Self { + Self { context } + } + + pub async fn with(&self, f: F) -> R + where + F: FnOnce(&icmp::Socket<'static>) -> R, + { + self.context.with(f).await + } + + pub async fn with_mutable(&self, f: F) -> R + where + F: FnOnce(&mut icmp::Socket<'static>) -> R, + { + self.context.with_mutable(f).await + } + + pub fn poll_with(&self, context: &mut Context<'_>, f: F) -> Poll + where + F: FnOnce(&icmp::Socket, &mut Context<'_>) -> Poll, + { + self.context.poll_with(context, f) + } + + pub fn poll_with_mutable(&self, context: &mut Context<'_>, f: F) -> Poll + where + F: FnOnce(&mut icmp::Socket, &mut Context<'_>) -> Poll, + { + self.context.poll_with_mutable(context, f) + } + + fn poll_send_to( + &self, + context: &mut Context<'_>, + buffer: &[u8], + remote_endpoint: &IpAddress, + ) -> Poll> { + self.poll_with_mutable(context, |socket, context| { + let send_capacity_too_small = socket.payload_send_capacity() < buffer.len(); + if send_capacity_too_small { + return Poll::Ready(Err(Error::PacketTooLarge)); + } + + let remote_endpoint = remote_endpoint.into_smoltcp(); + + match socket.send_slice(buffer, remote_endpoint) { + Ok(()) => Poll::Ready(Ok(())), + Err(icmp::SendError::BufferFull) => { + socket.register_send_waker(context.waker()); + Poll::Pending + } + Err(icmp::SendError::Unaddressable) => { + if socket.is_open() { + Poll::Ready(Err(Error::NoRoute)) + } else { + Poll::Ready(Err(Error::SocketNotBound)) + } + } + } + }) + } + + fn poll_receive_from( + &self, + context: &mut Context<'_>, + buffer: &mut [u8], + ) -> Poll> { + self.poll_with_mutable(context, |socket, context| match socket.recv_slice(buffer) { + Ok((size, remote_endpoint)) => { + let remote_endpoint = IpAddress::from_smoltcp(&remote_endpoint); + Poll::Ready(Ok((size, remote_endpoint))) + } + Err(icmp::RecvError::Truncated) => Poll::Ready(Err(Error::Truncated)), + Err(icmp::RecvError::Exhausted) => { + socket.register_recv_waker(context.waker()); + + self.context.stack.wake_runner(); + Poll::Pending + } + }) + } + + pub async fn bind(&self, endpoint: IcmpEndpoint) -> Result<()> { + let endpoint = endpoint.into_smoltcp(); + + self.with_mutable(|socket: &mut icmp::Socket| socket.bind(endpoint)) + .await?; + Ok(()) + } + + pub async fn can_write(&self) -> bool { + self.with(icmp::Socket::can_send).await + } + + pub async fn can_read(&self) -> bool { + self.with(icmp::Socket::can_recv).await + } + + pub async fn write_to(&self, buffer: &[u8], endpoint: impl Into) -> Result<()> { + let address: IpAddress = endpoint.into(); + + poll_fn(|context| self.poll_send_to(context, buffer, &address)).await + } + + pub async fn read_from(&self, buffer: &mut [u8]) -> Result<(usize, IpAddress)> { + poll_fn(|context| self.poll_receive_from(context, buffer)).await + } + + pub async fn read_from_with_timeout( + &self, + buffer: &mut [u8], + timeout: impl Into, + ) -> Result<(usize, IpAddress)> { + use embassy_futures::select::{Either, select}; + + let receive = poll_fn(|context| self.poll_receive_from(context, buffer)); + let sleep = task::sleep(timeout.into()); + + match select(receive, sleep).await { + Either::First(result) => result, + Either::Second(_) => Err(Error::TimedOut), + } + } + + /// Sends an ICMP echo request (ping) to the specified remote address and waits for a reply. + /// Returns the round-trip time if successful. + /// + /// # Errors + /// + /// Returns an error if the ping request fails or times out. + pub async fn ping( + &self, + remote_address: &IpAddress, + sequence_number: u16, + identifier: u16, + timeout: Duration, + payload_size: usize, + ) -> Result { + use wire::{Icmpv4Packet, Icmpv4Repr, Icmpv6Packet, Icmpv6Repr}; + + let mut echo_payload = vec![0u8; payload_size]; + let start_time = crate::get_smoltcp_time(); + + let timestamp_millis = start_time.total_millis() as u64; + echo_payload[0..8].copy_from_slice(×tamp_millis.to_be_bytes()); + + let mut stack_lock = self.context.stack.lock().await; + + let src_addr_v6 = if let IpAddress::IPv6(v6_addr) = remote_address { + Some( + stack_lock + .interface + .get_source_address_ipv6(&v6_addr.into_smoltcp()), + ) + } else { + None + }; + + let socket = stack_lock + .sockets + .get_mut::(self.context.handle); + + let remote_endpoint = remote_address.into_smoltcp(); + + let checksum_caps = smoltcp::phy::ChecksumCapabilities::default(); + + match remote_address { + IpAddress::IPv4(_) => { + let icmp_repr = Icmpv4Repr::EchoRequest { + ident: identifier, + seq_no: sequence_number, + data: &echo_payload, + }; + + let icmp_payload = socket + .send(icmp_repr.buffer_len(), remote_endpoint) + .map_err(|e| match e { + icmp::SendError::BufferFull => Error::ResourceBusy, + icmp::SendError::Unaddressable => Error::NoRoute, + })?; + + let mut icmp_packet = Icmpv4Packet::new_unchecked(icmp_payload); + icmp_repr.emit(&mut icmp_packet, &checksum_caps); + } + IpAddress::IPv6(v6_addr) => { + let icmp_repr = Icmpv6Repr::EchoRequest { + ident: identifier, + seq_no: sequence_number, + data: &echo_payload, + }; + + let icmp_payload = socket + .send(icmp_repr.buffer_len(), remote_endpoint) + .map_err(|e| match e { + icmp::SendError::BufferFull => Error::ResourceBusy, + icmp::SendError::Unaddressable => Error::NoRoute, + })?; + + let src_addr = src_addr_v6.unwrap(); + let mut icmp_packet = Icmpv6Packet::new_unchecked(icmp_payload); + icmp_repr.emit( + &src_addr, + &v6_addr.into_smoltcp(), + &mut icmp_packet, + &checksum_caps, + ); + } + } + + drop(stack_lock); + + self.context.stack.wake_runner(); + + let timeout_end = start_time + timeout.into_smoltcp(); + + loop { + let now = crate::get_smoltcp_time(); + if now >= timeout_end { + return Err(Error::TimedOut); + } + + let mut recv_buffer = [0u8; 256]; + let result = self.read_from_with_timeout(&mut recv_buffer, timeout).await; + + match result { + Ok((size, addr)) if addr == *remote_address => { + // Parse the received packet + let is_valid_reply = match remote_address { + IpAddress::IPv4(_) => { + if let Ok(packet) = Icmpv4Packet::new_checked(&recv_buffer[..size]) { + if let Ok(repr) = Icmpv4Repr::parse(&packet, &checksum_caps) { + matches!( + repr, + Icmpv4Repr::EchoReply { + ident: id, + seq_no, + .. + } if id == identifier && seq_no == sequence_number + ) + } else { + false + } + } else { + false + } + } + IpAddress::IPv6(v6_addr) => { + if let Ok(packet) = Icmpv6Packet::new_checked(&recv_buffer[..size]) { + let src_addr = self + .context + .stack + .with_mutable(|s| s.get_source_ip_v6_address(*v6_addr)) + .await; + + if let Ok(repr) = Icmpv6Repr::parse( + &v6_addr.into_smoltcp(), + &src_addr.into_smoltcp(), + &packet, + &checksum_caps, + ) { + matches!( + repr, + Icmpv6Repr::EchoReply { + ident: id, + seq_no, + .. + } if id == identifier && seq_no == sequence_number + ) + } else { + false + } + } else { + false + } + } + }; + + if is_valid_reply { + let end_time = crate::get_smoltcp_time(); + let rtt = end_time - start_time; + return Ok(Duration::from_milliseconds(rtt.total_millis() as u64)); + } + } + Ok(_) => { + continue; + } + Err(e) => return Err(e), + } + } + } + + pub async fn flush(&self) -> () { + poll_fn(|context| { + self.poll_with_mutable(context, |socket, context| { + if socket.send_queue() == 0 { + Poll::Ready(()) + } else { + socket.register_send_waker(context.waker()); + Poll::Pending + } + }) + }) + .await + } + + pub async fn is_open(&self) -> bool { + self.with(icmp::Socket::is_open).await + } + + pub async fn get_packet_read_capacity(&self) -> usize { + self.with(icmp::Socket::packet_recv_capacity).await + } + + pub async fn get_packet_write_capacity(&self) -> usize { + self.with(icmp::Socket::packet_send_capacity).await + } + + pub async fn get_payload_read_capacity(&self) -> usize { + self.with(icmp::Socket::payload_recv_capacity).await + } + + pub async fn get_payload_write_capacity(&self) -> usize { + self.with(icmp::Socket::payload_send_capacity).await + } + + pub async fn get_hop_limit(&self) -> Option { + self.with(icmp::Socket::hop_limit).await + } + + pub async fn set_hop_limit(&self, hop_limit: Option) -> () { + self.with_mutable(|socket: &mut icmp::Socket| { + socket.set_hop_limit(hop_limit); + }) + .await + } +} diff --git a/modules/network/src/socket/mod.rs b/modules/network/src/socket/mod.rs new file mode 100644 index 00000000..b9d852d6 --- /dev/null +++ b/modules/network/src/socket/mod.rs @@ -0,0 +1,9 @@ +mod dns; +mod icmp; +mod tcp; +mod udp; + +pub use dns::*; +pub use icmp::*; +pub use tcp::*; +pub use udp::*; diff --git a/modules/network/src/socket/tcp.rs b/modules/network/src/socket/tcp.rs new file mode 100644 index 00000000..bafc3abe --- /dev/null +++ b/modules/network/src/socket/tcp.rs @@ -0,0 +1,560 @@ +use core::{ + future::poll_fn, + task::{Context, Poll}, +}; + +use crate::{ + Duration, Error, IpAddress, IpEndpoint, IpListenEndpoint, Port, Result, SocketContext, +}; +use embassy_futures::block_on; +use smoltcp::socket::tcp; + +#[repr(transparent)] +pub struct TcpSocket { + context: SocketContext, +} + +impl TcpSocket { + pub(crate) fn new(context: SocketContext) -> Self { + Self { context } + } + pub async fn with(&self, f: F) -> R + where + F: FnOnce(&tcp::Socket<'static>) -> R, + { + self.context.with(f).await + } + + pub async fn with_mutable(&self, f: F) -> R + where + F: FnOnce(&mut tcp::Socket<'static>) -> R, + { + self.context.with_mutable(f).await + } + + pub fn poll_with(&self, context: &mut Context<'_>, f: F) -> Poll + where + F: FnOnce(&tcp::Socket, &mut Context<'_>) -> Poll, + { + self.context.poll_with(context, f) + } + + pub fn poll_with_mutable(&self, context: &mut Context<'_>, f: F) -> Poll + where + F: FnOnce(&mut tcp::Socket, &mut Context<'_>) -> Poll, + { + self.context.poll_with_mutable(context, f) + } + + pub async fn set_timeout(&mut self, timeout: Option) { + let timeout = timeout.map(Duration::into_smoltcp); + + self.with_mutable(|socket| socket.set_timeout(timeout)) + .await + } + + pub async fn accept( + &mut self, + address: Option>, + port: impl Into, + ) -> Result<()> { + let endpoint = IpListenEndpoint::new(address.map(Into::into), port.into()).into_smoltcp(); + + self.with_mutable(|s| { + if s.state() == tcp::State::Closed { + s.listen(endpoint).map_err(|e| match e { + tcp::ListenError::InvalidState => Error::InvalidState, + tcp::ListenError::Unaddressable => Error::InvalidPort, + }) + } else { + Ok(()) + } + }) + .await?; + + poll_fn(|cx| { + self.poll_with_mutable(cx, |s, cx| match s.state() { + tcp::State::Listen | tcp::State::SynSent | tcp::State::SynReceived => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + _ => Poll::Ready(Ok(())), + }) + }) + .await + } + + pub async fn connect( + &mut self, + address: impl Into, + port: impl Into, + ) -> Result<()> { + let endpoint = IpEndpoint::new(address.into(), port.into()).into_smoltcp(); + + self.context + .stack + .with_mutable(|stack| { + let local_port = stack.get_next_port().into_inner(); + + let socket: &mut tcp::Socket<'static> = stack.sockets.get_mut(self.context.handle); + + match socket.connect(stack.interface.context(), endpoint, local_port) { + Ok(()) => Ok(()), + Err(tcp::ConnectError::InvalidState) => Err(Error::InvalidState), + Err(tcp::ConnectError::Unaddressable) => Err(Error::NoRoute), + } + }) + .await?; + + poll_fn(|cx| { + self.poll_with_mutable(cx, |socket, cx| match socket.state() { + tcp::State::Closed | tcp::State::TimeWait => { + Poll::Ready(Err(Error::ConnectionReset)) + } + tcp::State::Listen => unreachable!(), + tcp::State::SynSent | tcp::State::SynReceived => { + socket.register_send_waker(cx.waker()); + Poll::Pending + } + _ => Poll::Ready(Ok(())), + }) + }) + .await + } + + pub async fn read(&mut self, buffer: &mut [u8]) -> Result { + poll_fn(|cx| { + self.poll_with_mutable(cx, |s, cx| match s.recv_slice(buffer) { + Ok(0) if buffer.is_empty() => Poll::Ready(Ok(0)), + Ok(0) => { + s.register_recv_waker(cx.waker()); + Poll::Pending + } + Ok(n) => Poll::Ready(Ok(n)), + Err(tcp::RecvError::Finished) => Poll::Ready(Ok(0)), + Err(tcp::RecvError::InvalidState) => Poll::Ready(Err(Error::ConnectionReset)), + }) + }) + .await + } + + pub async fn write(&mut self, buffer: &[u8]) -> Result { + poll_fn(|cx| { + self.poll_with_mutable(cx, |s, cx| match s.send_slice(buffer) { + Ok(0) => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + Ok(n) => Poll::Ready(Ok(n)), + Err(tcp::SendError::InvalidState) => Poll::Ready(Err(Error::ConnectionReset)), + }) + }) + .await + } + + pub async fn flush(&mut self) -> Result<()> { + poll_fn(|cx| { + self.poll_with_mutable(cx, |s, cx| { + let data_pending = (s.send_queue() > 0) && s.state() != tcp::State::Closed; + let fin_pending = matches!( + s.state(), + tcp::State::FinWait1 | tcp::State::Closing | tcp::State::LastAck + ); + let rst_pending = s.state() == tcp::State::Closed && s.remote_endpoint().is_some(); + + if data_pending || fin_pending || rst_pending { + s.register_send_waker(cx.waker()); + Poll::Pending + } else { + Poll::Ready(Ok(())) + } + }) + }) + .await + } + + pub async fn close(&mut self) { + self.context.closed = true; + self.with_mutable(tcp::Socket::close).await + } + + pub async fn close_forced(&mut self) { + self.context.closed = true; + self.with_mutable(tcp::Socket::abort).await + } + + pub async fn get_read_capacity(&self) -> usize { + self.with(tcp::Socket::recv_capacity).await + } + + pub async fn get_write_capacity(&self) -> usize { + self.with(tcp::Socket::send_capacity).await + } + + pub async fn get_write_queue_size(&self) -> usize { + self.with(tcp::Socket::send_queue).await + } + + pub async fn get_read_queue_size(&self) -> usize { + self.with(tcp::Socket::recv_queue).await + } + + pub async fn get_local_endpoint(&self) -> Result> { + let endpoint = self.with(tcp::Socket::local_endpoint).await; + + Ok(endpoint.map(|e| (IpAddress::from_smoltcp(&e.addr), Port::from_inner(e.port)))) + } + + pub async fn get_remote_endpoint(&self) -> Result> { + let endpoint = self.with(tcp::Socket::remote_endpoint).await; + + Ok(endpoint.map(|e| (IpAddress::from_smoltcp(&e.addr), Port::from_inner(e.port)))) + } + + pub async fn set_keep_alive(&mut self, keep_alive: Option) { + let keep_alive = keep_alive.map(Duration::into_smoltcp); + self.with_mutable(|socket| socket.set_keep_alive(keep_alive)) + .await + } + + pub async fn set_hop_limit(&mut self, hop_limit: Option) { + self.with_mutable(|socket| socket.set_hop_limit(hop_limit)) + .await + } + + pub async fn can_read(&self) -> bool { + self.with(tcp::Socket::can_recv).await + } + + pub async fn can_write(&self) -> bool { + self.with(tcp::Socket::can_send).await + } + + pub async fn may_read(&self) -> bool { + self.with(tcp::Socket::may_recv).await + } + + pub async fn may_write(&self) -> bool { + self.with(tcp::Socket::may_send).await + } +} + +impl Drop for TcpSocket { + fn drop(&mut self) { + if self.context.closed { + return; + } + log::warning!("TCP socket dropped without being closed. Forcing closure."); + block_on(self.with_mutable(tcp::Socket::close)); + } +} + +#[cfg(test)] +mod tests { + + extern crate std; + + use synchronization::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; + + use crate::tests::initialize; + + use super::*; + + static TEST_MUTEX: Mutex = Mutex::new(()); + + #[task::test] + async fn test_tcp_connect() { + let _lock = TEST_MUTEX.lock().await; + let network_manager = initialize().await; + let port = Port::from_inner(51001); + use synchronization::{Arc, blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal}; + let server_ready = Arc::new(Signal::::new()); + let connection_established = Arc::new(Signal::::new()); + let client_done = Arc::new(Signal::::new()); + let server_ready_clone = server_ready.clone(); + let connection_established_clone = connection_established.clone(); + let client_done_clone = client_done.clone(); + let mut listener = network_manager + .new_tcp_socket(1024, 1024, None) + .await + .expect("Failed to create listener socket"); + let task_manager = task::get_instance(); + let current_task = task_manager.get_current_task_identifier().await; + let (listen_task, _) = task_manager + .spawn(current_task, "TCP Listen Task", None, move |_| async move { + server_ready_clone.signal(()); + let accept_result = listener.accept(Some([127, 0, 0, 1]), port).await; + if accept_result.is_err() { + return; + } + connection_established_clone.signal(()); + client_done_clone.wait().await; + listener.close_forced().await; + }) + .await + .unwrap(); + for _ in 0..5 { + task::sleep(Duration::from_milliseconds(20)).await; + } + server_ready.wait().await; + task::sleep(Duration::from_milliseconds(200)).await; + let mut client = network_manager + .new_tcp_socket(1024, 1024, None) + .await + .expect("Failed to create client socket"); + let connect_result = client.connect([127, 0, 0, 1], port).await; + if connect_result.is_err() { + return; + } + connection_established.wait().await; + let _endpoint = client.get_local_endpoint().await; + task::sleep(Duration::from_milliseconds(100)).await; + client.close_forced().await; + client_done.signal(()); + listen_task.join().await; + } + + #[task::test] + async fn test_tcp_send_receive() { + use synchronization::{Arc, blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal}; + let _lock = TEST_MUTEX.lock().await; + let network_manager = initialize().await; + let port = Port::from_inner(51002); + let mut listener = network_manager + .new_tcp_socket(2048, 2048, None) + .await + .expect("Failed to create listener"); + let server_ready = Arc::new(Signal::::new()); + let server_ready_clone = server_ready.clone(); + let task_manager = task::get_instance(); + let current_task = task_manager.get_current_task_identifier().await; + let (_server_task, _) = task_manager + .spawn(current_task, "TCP Server Task", None, move |_| async move { + server_ready_clone.signal(()); + let accept_result = listener.accept(Some([127, 0, 0, 1]), port).await; + if accept_result.is_err() { + return; + } + let mut buffer = [0u8; 1024]; + match listener.read(&mut buffer).await { + Ok(size) => { + assert_eq!(&buffer[..size], b"Hello, TCP!", "Received data mismatch"); + } + Err(_) => { + return; + } + } + let response = b"Hello back!"; + if let Err(_) = listener.write(response).await { + return; + } + if let Err(_) = listener.flush().await { + return; + } + task::sleep(Duration::from_milliseconds(100)).await; + listener.close_forced().await; + }) + .await + .unwrap(); + server_ready.wait().await; + let mut client = network_manager + .new_tcp_socket(2048, 2048, None) + .await + .expect("Failed to create client"); + let connect_result = client.connect([127, 0, 0, 1], port).await; + if connect_result.is_err() { + return; + } + task::sleep(Duration::from_milliseconds(100)).await; + if let Err(_) = client.write(b"Hello, TCP!").await { + return; + } + if let Err(_) = client.flush().await { + return; + } + let mut response_buffer = [0u8; 1024]; + match client.read(&mut response_buffer).await { + Ok(size) => { + assert_eq!( + &response_buffer[..size], + b"Hello back!", + "Response data mismatch" + ); + } + Err(_) => { + return; + } + } + client.close_forced().await; + } + + #[task::test] + async fn test_tcp_endpoints() { + let _lock = TEST_MUTEX.lock().await; + let network_manager = initialize().await; + let mut socket = network_manager + .new_tcp_socket(1024, 1024, None) + .await + .expect("Failed to create socket"); + let local = socket + .get_local_endpoint() + .await + .expect("Failed to get local endpoint"); + let remote = socket + .get_remote_endpoint() + .await + .expect("Failed to get remote endpoint"); + assert!( + local.is_none(), + "TCP endpoint | Local endpoint should be None before connection" + ); + assert!( + remote.is_none(), + "Remote endpoint should be None before connection" + ); + let port = Port::from_inner(51003); + let mut listener = network_manager + .new_tcp_socket(1024, 1024, None) + .await + .expect("Failed to create listener"); + use synchronization::{Arc, blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal}; + let server_ready = Arc::new(Signal::::new()); + let connection_ready = Arc::new(Signal::::new()); + let endpoints_checked = Arc::new(Signal::::new()); + let server_ready_clone = server_ready.clone(); + let connection_ready_clone = connection_ready.clone(); + let endpoints_checked_clone = endpoints_checked.clone(); + let task_manager = task::get_instance(); + let current_task = task_manager.get_current_task_identifier().await; + let (listen_task, _) = task_manager + .spawn( + current_task, + "TCP Endpoint Listen", + None, + move |_| async move { + server_ready_clone.signal(()); + let accept_result = listener.accept(Some([127, 0, 0, 1]), port).await; + if accept_result.is_err() { + return; + } + connection_ready_clone.signal(()); + endpoints_checked_clone.wait().await; + task::sleep(Duration::from_milliseconds(100)).await; + listener.close_forced().await; + }, + ) + .await + .unwrap(); + for _ in 0..5 { + task::sleep(Duration::from_milliseconds(20)).await; + } + server_ready.wait().await; + task::sleep(Duration::from_milliseconds(200)).await; + let connect_result = socket.connect([127, 0, 0, 1], port).await; + if connect_result.is_err() { + return; + } + task::sleep(Duration::from_milliseconds(50)).await; + connection_ready.wait().await; + task::sleep(Duration::from_milliseconds(50)).await; + let local = socket + .get_local_endpoint() + .await + .expect("Failed to get local endpoint"); + let remote = socket + .get_remote_endpoint() + .await + .expect("Failed to get remote endpoint"); + assert!( + local.is_some(), + "Local endpoint should be set after connection" + ); + assert!( + remote.is_some(), + "Remote endpoint should be set after connection" + ); + if let Some((addr, p)) = remote { + assert_eq!( + addr, + IpAddress::from([127, 0, 0, 1]), + "Remote address mismatch" + ); + assert_eq!(p, port, "Remote port mismatch"); + } + endpoints_checked.signal(()); + task::sleep(Duration::from_milliseconds(100)).await; + socket.close_forced().await; + listen_task.join().await; + } + + #[task::test] + async fn test_tcp_capacities() { + let _lock = TEST_MUTEX.lock().await; + let network_manager = initialize().await; + let tx_buffer = 2048; + let rx_buffer = 1024; + let mut socket = network_manager + .new_tcp_socket(tx_buffer, rx_buffer, None) + .await + .expect("Failed to create socket"); + let read_cap = socket.get_read_capacity().await; + let write_cap = socket.get_write_capacity().await; + let read_queue = socket.get_read_queue_size().await; + let write_queue = socket.get_write_queue_size().await; + assert_eq!(read_cap, rx_buffer, "Read capacity mismatch"); + assert_eq!(write_cap, tx_buffer, "Write capacity mismatch"); + assert_eq!(read_queue, 0, "Read queue should be empty initially"); + assert_eq!(write_queue, 0, "Write queue should be empty initially"); + socket.close_forced().await; + } + + #[task::test] + async fn test_tcp_flush() { + let _lock = TEST_MUTEX.lock().await; + let network_manager = initialize().await; + let port = Port::from_inner(51004); + let mut listener = network_manager + .new_tcp_socket(1024, 1024, None) + .await + .expect("Failed to create listener"); + let task_manager = task::get_instance(); + let current_task = task_manager.get_current_task_identifier().await; + let (_server_task, _) = task_manager + .spawn( + current_task, + "TCP Flush Server", + None, + move |_| async move { + let accept_result = listener.accept(Some([127, 0, 0, 1]), port).await; + if accept_result.is_err() { + return; + } + let mut buffer = [0u8; 1024]; + let _ = listener.read(&mut buffer).await; + task::sleep(Duration::from_milliseconds(200)).await; + listener.close_forced().await; + }, + ) + .await + .unwrap(); + task::sleep(Duration::from_milliseconds(300)).await; + let mut client = network_manager + .new_tcp_socket(1024, 1024, None) + .await + .expect("Failed to create client"); + let connect_result = client.connect([127, 0, 0, 1], port).await; + if connect_result.is_err() { + return; + } + task::sleep(Duration::from_milliseconds(100)).await; + let write_result = client.write(b"Test data").await; + if write_result.is_err() { + return; + } + if let Err(_) = client.flush().await { + return; + } + task::sleep(Duration::from_milliseconds(200)).await; + client.close_forced().await; + } +} diff --git a/modules/network/src/socket/udp.rs b/modules/network/src/socket/udp.rs new file mode 100644 index 00000000..735c18ae --- /dev/null +++ b/modules/network/src/socket/udp.rs @@ -0,0 +1,369 @@ +use core::{ + future::poll_fn, + task::{Context, Poll}, +}; + +use embassy_futures::block_on; +use smoltcp::socket::udp; + +use crate::{Error, IpAddress, Port, Result, SocketContext, UdpMetadata}; + +pub struct UdpSocket { + context: SocketContext, +} + +impl UdpSocket { + pub(crate) fn new(context: SocketContext) -> Self { + Self { context } + } + + pub async fn with(&self, f: F) -> R + where + F: FnOnce(&udp::Socket<'static>) -> R, + { + self.context.with(f).await + } + + pub async fn with_mutable(&self, f: F) -> R + where + F: FnOnce(&mut udp::Socket<'static>) -> R, + { + self.context.with_mutable(f).await + } + + pub fn poll_with(&self, context: &mut Context<'_>, f: F) -> Poll + where + F: FnOnce(&udp::Socket, &mut Context<'_>) -> Poll, + { + self.context.poll_with(context, f) + } + + pub fn poll_with_mutable(&self, context: &mut Context<'_>, f: F) -> Poll + where + F: FnOnce(&mut udp::Socket, &mut Context<'_>) -> Poll, + { + self.context.poll_with_mutable(context, f) + } + + pub async fn bind(&mut self, port: Port) -> Result<()> { + let port = port.into_inner(); + + self.with_mutable(|socket| socket.bind(port)).await?; + + Ok(()) + } + + pub fn poll_send_to( + &mut self, + context: &mut Context<'_>, + buffer: &[u8], + metadata: &UdpMetadata, + ) -> Poll> { + log::debug!("UDP poll_send_to: sending {} bytes", buffer.len()); + self.poll_with_mutable(context, |socket, cx| { + let capacity = socket.payload_send_capacity(); + log::debug!("UDP send capacity: {}, needed: {}", capacity, buffer.len()); + + if capacity < buffer.len() { + log::warning!( + "UDP send buffer too small: capacity={}, needed={}", + capacity, + buffer.len() + ); + return Poll::Ready(Err(Error::PacketTooLarge)); + } + + let metadata = metadata.to_smoltcp(); + + match socket.send_slice(buffer, metadata) { + Ok(()) => { + log::debug!("UDP send_slice succeeded"); + Poll::Ready(Ok(())) + } + Err(udp::SendError::BufferFull) => { + log::information!("UDP send buffer full, registering waker"); + socket.register_send_waker(cx.waker()); + Poll::Pending + } + Err(udp::SendError::Unaddressable) => { + if socket.endpoint().port == 0 { + log::error!("UDP send failed: socket not bound"); + Poll::Ready(Err(Error::SocketNotBound)) + } else { + log::error!("UDP send failed: no route"); + Poll::Ready(Err(Error::NoRoute)) + } + } + } + }) + } + + pub fn poll_receive_from( + &self, + context: &mut Context<'_>, + buffer: &mut [u8], + ) -> Poll> { + log::debug!("UDP poll_receive_from: buffer size={}", buffer.len()); + self.poll_with_mutable(context, |socket, cx| { + log::debug!( + "UDP recv: checking socket for data (can_recv={}, buffered={})", + socket.can_recv(), + socket.recv_queue() + ); + match socket.recv_slice(buffer) { + Ok((n, meta)) => { + log::information!("UDP received {} bytes", n); + Poll::Ready(Ok((n, UdpMetadata::from_smoltcp(&meta)))) + } + Err(udp::RecvError::Truncated) => { + log::warning!("UDP receive truncated"); + Poll::Ready(Err(Error::Truncated)) + } + Err(udp::RecvError::Exhausted) => { + log::information!( + "UDP receive buffer exhausted (can_recv={}, buffered={})", + socket.can_recv(), + socket.recv_queue() + ); + socket.register_recv_waker(cx.waker()); + log::information!("UDP receive waker registered"); + Poll::Pending + } + } + }) + } + + pub async fn write_to(&mut self, buffer: &[u8], metadata: &UdpMetadata) -> Result<()> { + log::debug!( + "UDP write_to: starting async send of {} bytes", + buffer.len() + ); + let result = poll_fn(|cx| self.poll_send_to(cx, buffer, metadata)).await; + log::debug!("UDP write_to: completed with result {:?}", result.is_ok()); + result + } + + pub async fn read_from(&self, buffer: &mut [u8]) -> Result<(usize, UdpMetadata)> { + poll_fn(|cx| { + log::information!("Polling UDP read"); + let r = self.poll_receive_from(cx, buffer); + log::information!("UDP read poll completed"); + r + }) + .await + } + + pub fn flush(&mut self) -> impl Future + '_ { + poll_fn(|cx| { + self.poll_with_mutable(cx, |socket, cx| { + if socket.can_send() { + Poll::Ready(()) + } else { + socket.register_send_waker(cx.waker()); + Poll::Pending + } + }) + }) + } + + pub async fn close(mut self) { + self.context.closed = true; + self.with_mutable(|s| { + log::information!("Closing UDP socket : {:?}", s.endpoint()); + udp::Socket::close(s); + log::information!("UDP socket closed"); + }) + .await; + } + + pub async fn get_endpoint(&self) -> Result<(Option, Port)> { + let endpoint = self.with(udp::Socket::endpoint).await; + + let ip_address = endpoint.addr.as_ref().map(IpAddress::from_smoltcp); + let port = Port::from_inner(endpoint.port); + + Ok((ip_address, port)) + } + + pub async fn get_packet_read_capacity(&self) -> usize { + self.with(udp::Socket::packet_recv_capacity).await + } + + pub async fn get_packet_write_capacity(&self) -> usize { + self.with(udp::Socket::packet_send_capacity).await + } + + pub async fn get_payload_read_capacity(&self) -> usize { + self.with(udp::Socket::payload_recv_capacity).await + } + + pub async fn get_payload_write_capacity(&self) -> usize { + self.with(udp::Socket::payload_send_capacity).await + } + + pub async fn set_hop_limit(&mut self, hop_limit: Option) { + self.with_mutable(|socket| socket.set_hop_limit(hop_limit)) + .await + } +} + +impl Drop for UdpSocket { + fn drop(&mut self) { + if self.context.closed { + return; + } + log::warning!("UDP socket dropped without being closed. Forcing closure."); + block_on(self.with_mutable(udp::Socket::close)); + } +} + +#[cfg(test)] +mod tests { + + extern crate std; + + use crate::tests::initialize; + + use super::*; + use smoltcp::phy::PacketMeta; + + #[task::test] + async fn test_udp_bind() { + let network_manager = initialize().await; + + let mut socket = network_manager + .new_udp_socket(1024, 1024, 10, 10, None) + .await + .expect("Failed to create UDP socket"); + + let port = Port::from_inner(10001); + let result = socket.bind(port).await; + + assert!(result.is_ok(), "Failed to bind UDP socket"); + + let (_ip, bound_port) = socket.get_endpoint().await.expect("Failed to get endpoint"); + assert_eq!(bound_port, port, "Port mismatch"); + + socket.close().await; + } + + #[task::test] + async fn test_udp_send_receive() { + let network_manager = initialize().await; + + // Create sender socket + let mut sender = network_manager + .new_udp_socket(1024, 1024, 10, 10, None) + .await + .expect("Failed to create sender socket"); + + // Create receiver socket + let mut receiver = network_manager + .new_udp_socket(65535, 65535, 10, 10, None) + .await + .expect("Failed to create receiver socket"); + + // Bind receiver to a specific port + let receiver_port = Port::from_inner(10003); + receiver + .bind(receiver_port) + .await + .expect("Failed to bind receiver"); + + // Prepare test data + let test_data = b"Hello, UDP!"; + + let remote_ip: IpAddress = [127, 0, 0, 1].into(); + + let metadata = UdpMetadata::new(remote_ip, receiver_port, None, PacketMeta::default()); + + sender + .bind(Port::from_inner(10002)) + .await + .expect("Failed to bind sender"); + + log::information!("Sending data"); + + // Send data + let send_result = sender.write_to(test_data, &metadata).await; + assert_eq!(send_result, Ok(())); + + log::information!("Data sent, waiting to receive..."); + + // Receive data + let mut buffer = [0u8; 1024]; + let receive_result = receiver.read_from(&mut buffer).await; + + log::information!("Data received"); + + if let Ok((size, _recv_metadata)) = receive_result { + assert_eq!(size, test_data.len(), "Received data size mismatch"); + assert_eq!(&buffer[..size], test_data, "Received data mismatch"); + } + + sender.close().await; + receiver.close().await; + } + + #[task::test] + async fn test_udp_endpoint() { + let network_manager = initialize().await; + + let mut socket = network_manager + .new_udp_socket(1024, 1024, 10, 10, None) + .await + .expect("Failed to create UDP socket"); + + // Before binding, endpoint should have port 0 + let (_ip, port) = socket + .get_endpoint() + .await + .expect("Failed to get initial endpoint"); + assert_eq!(port.into_inner(), 0, "Initial port should be 0"); + + // After binding, endpoint should have the bound port + let bind_port = Port::from_inner(10004); + socket.bind(bind_port).await.expect("Failed to bind"); + + let (_, bound_port) = socket + .get_endpoint() + .await + .expect("Failed to get bound endpoint"); + assert_eq!(bound_port, bind_port, "Bound port mismatch"); + + socket.close().await; + } + + #[task::test] + async fn test_udp_capacities() { + let network_manager = initialize().await; + + let tx_buffer = 2048; + let rx_buffer = 1024; + let rx_meta = 15; + let tx_meta = 20; + + let socket = network_manager + .new_udp_socket(tx_buffer, rx_buffer, rx_meta, tx_meta, None) + .await + .expect("Failed to create UDP socket"); + + let packet_read_cap = socket.get_packet_read_capacity().await; + let packet_write_cap = socket.get_packet_write_capacity().await; + let payload_read_cap = socket.get_payload_read_capacity().await; + let payload_write_cap = socket.get_payload_write_capacity().await; + + assert_eq!(packet_read_cap, rx_meta, "Packet read capacity mismatch"); + assert_eq!(packet_write_cap, tx_meta, "Packet write capacity mismatch"); + assert_eq!( + payload_read_cap, rx_buffer, + "Payload read capacity mismatch" + ); + assert_eq!( + payload_write_cap, tx_buffer, + "Payload write capacity mismatch" + ); + + socket.close().await; + } +} diff --git a/modules/network/src/traits.rs b/modules/network/src/traits.rs deleted file mode 100644 index 3387901a..00000000 --- a/modules/network/src/traits.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::{IP, Port, Protocol}; -use file_system::Context; -use time::Duration; - -use crate::Result; - -pub trait SocketDriver: Send + Sync { - fn close(&self, context: &mut Context) -> Result<()>; - - fn bind(&self, ip: IP, port: Port, protocol: Protocol, context: &mut Context) -> Result<()>; - - fn connect(&self, ip: IP, port: Port, context: &mut Context) -> Result<()>; - - fn accept(&self, context: &mut Context) -> Result<(IP, Port)>; - - fn send(&self, context: &mut Context, data: &[u8]) -> Result<()>; - - fn send_to(&self, context: &mut Context, data: &[u8], ip: IP, port: Port) -> Result<()>; - - fn receive(&self, context: &mut Context, data: &mut [u8]) -> Result; - - fn receive_from(&self, context: &mut Context, data: &mut [u8]) -> Result<(usize, IP, Port)>; - - fn get_local_address(&self, context: &mut Context) -> Result<(IP, Port)>; - - fn get_remote_address(&self, context: &mut Context) -> Result<(IP, Port)>; - - fn set_send_timeout(&self, context: &mut Context, timeout: Duration) -> Result<()>; - - fn set_receive_timeout(&self, context: &mut Context, timeout: Duration) -> Result<()>; - - fn get_send_timeout(&self, context: &mut Context) -> Result>; - - fn get_receive_timeout(&self, context: &mut Context) -> Result>; -} - -mod tests {} diff --git a/modules/shared/src/any.rs b/modules/shared/src/any.rs index 9670442b..20a1769e 100644 --- a/modules/shared/src/any.rs +++ b/modules/shared/src/any.rs @@ -19,11 +19,13 @@ impl<'a, T> From<&'a T> for &'a AnyByLayout { impl AnyByLayout { pub const NONE: &mut Self = Self::from_mutable(&mut [0u8; 0]); - /// Gets a mutable reference to an `AnyByLayout` from raw parts. + /// Creates an `AnyByLayout` from raw parts. /// /// # Safety - /// The caller must ensure that the provided data pointer is valid for reads and writes - /// for the specified size, and that the memory is properly aligned. + /// + /// This function is unsafe because it creates a reference from a raw pointer. + /// The caller must ensure that the pointer is valid for reads and writes + /// for `size` bytes and properly aligned. pub unsafe fn from_raw_parts<'a>(data: *mut u8, size: usize) -> &'a mut Self { unsafe { let slice = slice::from_raw_parts_mut(data, size); diff --git a/modules/shared/src/lib.rs b/modules/shared/src/lib.rs index 9e71b392..7cb22fe7 100644 --- a/modules/shared/src/lib.rs +++ b/modules/shared/src/lib.rs @@ -8,6 +8,7 @@ pub mod flags; mod http; mod size; mod slice; +pub mod task; mod time; mod unit; mod utf8; diff --git a/modules/shared/src/task.rs b/modules/shared/src/task.rs new file mode 100644 index 00000000..f6b82a9d --- /dev/null +++ b/modules/shared/src/task.rs @@ -0,0 +1,18 @@ +#[macro_export] +macro_rules! poll_ready { + ($expr:expr) => { + match $expr { + Poll::Ready(val) => val, + Poll::Pending => { + return Poll::Pending; + } + } + }; +} + +#[macro_export] +macro_rules! poll_pin_ready { + ($pin:expr, $context:expr) => { + $crate::poll_ready!(::core::pin::pin!($pin).poll($context)) + }; +} diff --git a/modules/task/src/lib.rs b/modules/task/src/lib.rs index affa8acd..2f86ee6e 100644 --- a/modules/task/src/lib.rs +++ b/modules/task/src/lib.rs @@ -25,8 +25,8 @@ pub use task::*; pub use task_macros::{run, test}; /// Sleep the current thread for a given duration. -pub async fn sleep(duration: Duration) { - let nano_seconds = duration.as_nanos(); +pub async fn sleep(duration: impl Into) { + let nano_seconds = duration.into().as_nanos(); Timer::after(embassy_time::Duration::from_nanos(nano_seconds as u64)).await } diff --git a/modules/testing/Cargo.toml b/modules/testing/Cargo.toml index eea1b406..589c8882 100644 --- a/modules/testing/Cargo.toml +++ b/modules/testing/Cargo.toml @@ -19,3 +19,4 @@ drivers_shared = { workspace = true } drivers_std = { workspace = true } executable = { workspace = true } abi_definitions = { workspace = true } +network = { workspace = true } diff --git a/modules/testing/src/lib.rs b/modules/testing/src/lib.rs index 95909715..0dd410b9 100644 --- a/modules/testing/src/lib.rs +++ b/modules/testing/src/lib.rs @@ -9,11 +9,12 @@ use drivers_native::window_screen; use drivers_shared::devices::RandomDevice; use drivers_std::{devices::TimeDevice, log::Logger}; use executable::Standard; -use file_system::MemoryDevice; +use file_system::{AccessFlags, MemoryDevice}; +use network::{ADD_DNS_SERVER, ADD_IP_ADDRESS, ADD_ROUTE}; use users::GroupIdentifier; -use virtual_file_system::{ItemStatic, create_default_hierarchy, mount_static}; +use virtual_file_system::{File, ItemStatic, create_default_hierarchy, mount_static}; -pub async fn initialize(graphics_enabled: bool) -> Standard { +pub async fn initialize(graphics_enabled: bool, network_enabled: bool) -> Standard { log::initialize(&Logger).unwrap(); let task_manager = task::initialize(); @@ -71,7 +72,7 @@ pub async fn initialize(graphics_enabled: bool) -> Standard { let file_system = little_fs::FileSystem::new_format(memory_device, 256).unwrap(); let virtual_file_system = - virtual_file_system::initialize(task_manager, users, time, file_system, None).unwrap(); + virtual_file_system::initialize(task_manager, users, time, file_system).unwrap(); let task = task_manager.get_current_task_identifier().await; @@ -88,31 +89,44 @@ pub async fn initialize(graphics_enabled: bool) -> Standard { .await .unwrap(); - let group_identifier = GroupIdentifier::new(1000); + if network_enabled { + let network_manager = network::initialize( + task_manager, + virtual_file_system, + &drivers_shared::devices::RandomDevice, + ); - authentication::create_group(virtual_file_system, "administrator", Some(group_identifier)) - .await - .unwrap(); + let (interface_device, controller_device) = + drivers_std::tuntap::new("xila0", false, true).unwrap(); - authentication::create_user( - virtual_file_system, - "administrator", - "", - group_identifier, - None, - ) - .await - .unwrap(); + network_manager + .mount_interface(task, "tunnel0", interface_device, controller_device, None) + .await + .expect("Failed to mount network interface."); - task_manager - .set_environment_variable(task, "Paths", "/") + let mut file = File::open( + virtual_file_system, + task, + "/devices/network/tunnel0", + AccessFlags::READ_WRITE.into(), + ) .await - .unwrap(); + .expect("Failed to open network interface file."); - task_manager - .set_environment_variable(task, "Host", "xila") - .await - .unwrap(); + for ip_cidr in drivers_std::tuntap::IP_ADDRESSES { + file.control(ADD_IP_ADDRESS, ip_cidr).await.ok(); + } + + for route in drivers_std::tuntap::ROUTES { + file.control(ADD_ROUTE, route).await.ok(); + } + + for dns_server in drivers_std::tuntap::DEFAULT_DNS_SERVERS { + file.control(ADD_DNS_SERVER, dns_server).await.ok(); + } + + file.close(virtual_file_system).await.unwrap(); + } mount_static!( virtual_file_system, @@ -149,6 +163,32 @@ pub async fn initialize(graphics_enabled: bool) -> Standard { .await .unwrap(); + let group_identifier = GroupIdentifier::new(1000); + + authentication::create_group(virtual_file_system, "administrator", Some(group_identifier)) + .await + .unwrap(); + + authentication::create_user( + virtual_file_system, + "administrator", + "", + group_identifier, + None, + ) + .await + .unwrap(); + + task_manager + .set_environment_variable(task, "Paths", "/") + .await + .unwrap(); + + task_manager + .set_environment_variable(task, "Host", "xila") + .await + .unwrap(); + Standard::open( &"/devices/standard_in", &"/devices/standard_out", diff --git a/modules/virtual_file_system/Cargo.toml b/modules/virtual_file_system/Cargo.toml index a88517d8..ffd18ca0 100644 --- a/modules/virtual_file_system/Cargo.toml +++ b/modules/virtual_file_system/Cargo.toml @@ -8,7 +8,6 @@ file_system = { workspace = true } task = { workspace = true } users = { workspace = true } time = { workspace = true } -network = { workspace = true } synchronization = { workspace = true } shared = { workspace = true } log = { workspace = true } diff --git a/modules/virtual_file_system/src/directory.rs b/modules/virtual_file_system/src/directory.rs index e6cc18fa..2a69268b 100644 --- a/modules/virtual_file_system/src/directory.rs +++ b/modules/virtual_file_system/src/directory.rs @@ -24,16 +24,16 @@ impl Directory { Self(SynchronousDirectory::new(directory, flags, context)) } - pub async fn create<'a>( - virtual_file_system: &'a VirtualFileSystem<'a>, + pub async fn create( + virtual_file_system: &VirtualFileSystem, task: TaskIdentifier, path: impl AsRef, ) -> Result<()> { virtual_file_system.create_directory(task, &path).await } - pub async fn open<'a>( - virtual_file_system: &'a VirtualFileSystem<'a>, + pub async fn open( + virtual_file_system: &VirtualFileSystem, task: TaskIdentifier, path: impl AsRef, ) -> Result { @@ -56,7 +56,7 @@ impl Directory { poll(|| self.0.set_position(0)).await } - pub async fn close(mut self, virtual_file_system: &VirtualFileSystem<'_>) -> Result<()> { + pub async fn close(mut self, virtual_file_system: &VirtualFileSystem) -> Result<()> { let result = virtual_file_system .close( &ItemStatic::Directory(self.0.directory), diff --git a/modules/virtual_file_system/src/error.rs b/modules/virtual_file_system/src/error.rs index 2e787ea8..f3883776 100644 --- a/modules/virtual_file_system/src/error.rs +++ b/modules/virtual_file_system/src/error.rs @@ -17,7 +17,6 @@ pub enum Error { AlreadyExists, Time(time::Error), FileSystem(file_system::Error) = 0x100, - Network(network::Error) = 0x200, Users(users::Error) = 0x300, Task(task::Error) = 0x400, MissingAttribute, @@ -48,7 +47,6 @@ impl From for NonZeroU32 { let offset = match value { Error::FileSystem(error_type) => error_type.get_discriminant().get(), - Error::Network(error_type) => error_type.get_discriminant().get() as u32, _ => 0, }; @@ -74,12 +72,6 @@ impl From for Error { } } -impl From for Error { - fn from(value: network::Error) -> Self { - Self::Network(value) - } -} - impl From for Error { fn from(value: task::Error) -> Self { Self::Task(value) @@ -98,7 +90,6 @@ impl Display for Error { write!(f, "Failed to get task informations") } Error::FileSystem(err) => write!(f, "File system error: {err}"), - Error::Network(err) => write!(f, "Network error: {err}"), Error::InvalidIdentifier => write!(f, "Invalid identifier"), Error::AlreadyExists => write!(f, "Already exists"), Error::Time(err) => write!(f, "Time error: {err}"), diff --git a/modules/virtual_file_system/src/file.rs b/modules/virtual_file_system/src/file.rs index 14a07c65..64f863ed 100644 --- a/modules/virtual_file_system/src/file.rs +++ b/modules/virtual_file_system/src/file.rs @@ -45,8 +45,8 @@ impl File { Self(SynchronousFile::new(item, 0, flags, context)) } - pub async fn open<'a>( - virtual_file_system: &'a VirtualFileSystem<'a>, + pub async fn open( + virtual_file_system: &VirtualFileSystem, task: task::TaskIdentifier, path: impl AsRef, flags: Flags, @@ -56,8 +56,8 @@ impl File { Ok(file_identifier) } - pub async fn create_unnamed_pipe<'a>( - file_system: &'a VirtualFileSystem<'a>, + pub async fn create_unnamed_pipe( + file_system: &VirtualFileSystem, size: usize, status: StateFlags, ) -> Result<(Self, Self)> { @@ -75,7 +75,7 @@ impl File { } pub async fn read_slice_from_path( - virtual_file_system: &VirtualFileSystem<'_>, + virtual_file_system: &VirtualFileSystem, task: TaskIdentifier, path: impl AsRef, buffer: &mut [u8], @@ -96,7 +96,7 @@ impl File { } pub async fn read_from_path( - virtual_file_system: &VirtualFileSystem<'_>, + virtual_file_system: &VirtualFileSystem, task: TaskIdentifier, path: impl AsRef, buffer: &mut Vec, @@ -119,7 +119,7 @@ impl File { } pub async fn write_to_path( - virtual_file_system: &VirtualFileSystem<'_>, + virtual_file_system: &VirtualFileSystem, task: task::TaskIdentifier, path: impl AsRef, buffer: &[u8], @@ -210,7 +210,7 @@ impl File { Ok(self.0.flags.get_access()) } - pub async fn close(mut self, virtual_file_system: &VirtualFileSystem<'_>) -> crate::Result<()> { + pub async fn close(mut self, virtual_file_system: &VirtualFileSystem) -> crate::Result<()> { let result = virtual_file_system .close(&self.0.item, &mut self.0.context) .await; diff --git a/modules/virtual_file_system/src/file_system/mod.rs b/modules/virtual_file_system/src/file_system/mod.rs index 83bad7f8..cfcca582 100644 --- a/modules/virtual_file_system/src/file_system/mod.rs +++ b/modules/virtual_file_system/src/file_system/mod.rs @@ -1,7 +1,7 @@ mod utilities; use crate::pipe::Pipe; -use crate::{Directory, Error, File, ItemStatic, Result, SockerAddress, poll}; +use crate::{Directory, Error, File, ItemStatic, Result, poll}; use alloc::borrow::ToOwned; use alloc::vec; use alloc::{boxed::Box, collections::BTreeMap}; @@ -13,12 +13,10 @@ use file_system::{ AccessFlags, AttributeFlags, Attributes, Context, FileSystemOperations, Flags, Kind, Path, StateFlags, Statistics, }; -use network::{IP, Port, Protocol, SocketDriver}; use synchronization::{ blocking_mutex::raw::CriticalSectionRawMutex, once_lock::OnceLock, rwlock::RwLock, }; use task::TaskIdentifier; -use time::Duration; use users::{GroupIdentifier, UserIdentifier}; use utilities::*; @@ -30,20 +28,14 @@ pub fn initialize( users_manager: &'static users::Manager, time_manager: &'static time::Manager, root_file_system: impl FileSystemOperations + 'static, - network_socket_driver: Option<&'static dyn SocketDriver>, -) -> Result<&'static VirtualFileSystem<'static>> { - let virtual_file_system = VirtualFileSystem::new( - task_manager, - users_manager, - time_manager, - root_file_system, - network_socket_driver, - ); +) -> Result<&'static VirtualFileSystem> { + let virtual_file_system = + VirtualFileSystem::new(task_manager, users_manager, time_manager, root_file_system); Ok(VIRTUAL_FILE_SYSTEM_INSTANCE.get_or_init(|| virtual_file_system)) } -pub fn get_instance() -> &'static VirtualFileSystem<'static> { +pub fn get_instance() -> &'static VirtualFileSystem { VIRTUAL_FILE_SYSTEM_INSTANCE .try_get() .expect("Virtual file system is not initialized") @@ -52,7 +44,7 @@ pub fn get_instance() -> &'static VirtualFileSystem<'static> { /// The virtual file system. /// /// It is a singleton. -pub struct VirtualFileSystem<'a> { +pub struct VirtualFileSystem { /// Mounted file systems. file_systems: RwLock, /// Character devices. @@ -61,17 +53,14 @@ pub struct VirtualFileSystem<'a> { block_device: RwLock, /// Pipes. pipes: RwLock, - /// Network sockets. - _network_socket_driver: Option<&'a dyn SocketDriver>, } -impl<'a> VirtualFileSystem<'a> { +impl VirtualFileSystem { pub fn new( _: &'static task::Manager, _: &'static users::Manager, _: &'static time::Manager, root_file_system: impl FileSystemOperations + 'static, - _network_socket_driver: Option<&'a dyn SocketDriver>, ) -> Self { let file_systems = vec![InternalFileSystem { reference_count: 1, @@ -84,7 +73,6 @@ impl<'a> VirtualFileSystem<'a> { character_device: RwLock::new(BTreeMap::new()), block_device: RwLock::new(BTreeMap::new()), pipes: RwLock::new(BTreeMap::new()), - _network_socket_driver, } } @@ -213,13 +201,10 @@ impl<'a> VirtualFileSystem<'a> { let mut file_systems = self.file_systems.write().await; // Get the file systems - let (file_system, relative_path, _) = - Self::get_mutable_file_system_from_path(&mut file_systems, &path)?; // Get the file system identifier and the relative path - let (time, user, _) = self.get_time_user_group(task).await?; Self::check_permissions_with_parent( - file_system.file_system, + &file_systems, path, Permission::Read, Permission::Execute, @@ -227,13 +212,16 @@ impl<'a> VirtualFileSystem<'a> { ) .await?; + let (file_system, relative_path, _) = + Self::get_mutable_file_system_from_path(&mut file_systems, &path)?; // Get the file system identifier and the relative path + let mut attributes = Attributes::new().set_mask( AttributeFlags::Kind | AttributeFlags::User | AttributeFlags::Group | AttributeFlags::Permissions, ); - Self::get_attributes(file_system.file_system, path, &mut attributes).await?; + Self::get_attributes(file_system.file_system, relative_path, &mut attributes).await?; let kind = attributes.get_kind().ok_or(Error::MissingAttribute)?; if *kind != Kind::Directory { @@ -271,20 +259,23 @@ impl<'a> VirtualFileSystem<'a> { let mut file_systems = self.file_systems.write().await; // Get the file systems - let (file_system, relative_path, _) = - Self::get_mutable_file_system_from_path(&mut file_systems, &path)?; // Get the file system identifier and the relative path - let (time, user, group) = self.get_time_user_group(task).await?; let (mode, open, _) = flags.split(); if open.contains(CreateFlags::Create) { + let (file_system, relative_path, _) = + Self::get_mutable_file_system_from_path(&mut file_systems, &path)?; // Get the file system identifier and the relative path + let result = poll(|| Ok(file_system.file_system.create_file(relative_path)?)).await; match result { Ok(()) => { Self::check_permissions( file_system.file_system, - path.go_parent().ok_or(Error::InvalidPath)?, + path.go_parent().ok_or_else(|| { + log::error!("Error getting parent path for {:?}", path); + Error::InvalidPath + })?, Permission::Write | Permission::Execute, user, ) @@ -313,7 +304,7 @@ impl<'a> VirtualFileSystem<'a> { } } else { Self::check_permissions_with_parent( - file_system.file_system, + &file_systems, path, mode.into_permission(), Permission::Execute, @@ -322,6 +313,9 @@ impl<'a> VirtualFileSystem<'a> { .await?; } + let (file_system, relative_path, _) = + Self::get_mutable_file_system_from_path(&mut file_systems, &path)?; // Get the file system identifier and the relative path + let attributes = if mode.contains(AccessFlags::Write) { Attributes::new().set_modification(time).set_access(time) } else { @@ -394,7 +388,7 @@ impl<'a> VirtualFileSystem<'a> { path: impl AsRef, item: ItemStatic, ) -> Result<()> { - let path = path.as_ref(); + let path: &Path = path.as_ref(); if !path.is_valid() || !path.is_absolute() || path.is_root() { return Err(Error::InvalidPath); } @@ -406,9 +400,11 @@ impl<'a> VirtualFileSystem<'a> { let (_, user, _) = self.get_time_user_group(task).await?; + let parent_path = path.go_parent().ok_or(Error::InvalidPath)?; + Self::check_permissions( parent_file_system.file_system, - path.go_parent().ok_or(Error::InvalidPath)?, + parent_path, Permission::Write, user, ) @@ -447,15 +443,7 @@ impl<'a> VirtualFileSystem<'a> { ); inode } - ItemStatic::FileSystem(file_system) => { - let mut file_systems = self.file_systems.write().await; - file_systems.push(InternalFileSystem { - reference_count: 1, - mount_point: path.to_owned(), - file_system, - }); - 0 - } + ItemStatic::FileSystem(_) => 0, ItemStatic::Pipe(pipe) => { let mut pipes = self.pipes.write().await; let inode = Self::get_new_inode(&*pipes).ok_or(Error::TooManyInodes)?; @@ -483,6 +471,7 @@ impl<'a> VirtualFileSystem<'a> { return Err(Error::InvalidIdentifier); } }; + let attributes = Attributes::new() .set_user(user) .set_group(group) @@ -495,6 +484,14 @@ impl<'a> VirtualFileSystem<'a> { .set_inode(inode); Self::set_attributes(parent_file_system.file_system, relative_path, &attributes).await?; + if let ItemStatic::FileSystem(file_system) = item { + file_systems.push(InternalFileSystem { + reference_count: 1, + mount_point: path.to_owned(), + file_system, + }); + } + poll(|| { Ok(item .as_mount_operations() @@ -606,16 +603,10 @@ impl<'a> VirtualFileSystem<'a> { let inode = *attributes.get_inode().ok_or(Error::MissingAttribute)?; let kind = attributes.get_kind().ok_or(Error::MissingAttribute)?; - match kind { - Kind::Directory => { - return Err(Error::UnsupportedOperation); - } - Kind::Pipe => { - let mut named_pipes = self.pipes.write().await; + if kind == &Kind::Pipe { + let mut named_pipes = self.pipes.write().await; - named_pipes.remove(&inode); - } - _ => {} + named_pipes.remove(&inode); } poll(|| Ok(file_system.file_system.remove(relative_path)?)).await?; @@ -842,53 +833,4 @@ impl<'a> VirtualFileSystem<'a> { let attributes = Attributes::new().set_permissions(permissions); Self::set_attributes(file_system.file_system, relative_path, &attributes).await } - - pub async fn send(&self, _task: TaskIdentifier, _data: &[u8]) -> Result<()> { - todo!() - } - - pub async fn receive(&self, _task: TaskIdentifier, _data: &mut [u8]) -> Result { - todo!() - } - - pub async fn send_to( - &self, - _task: TaskIdentifier, - _data: &[u8], - _address: SockerAddress, - ) -> Result<()> { - todo!() - } - - pub async fn receive_from(&self, _data: &mut [u8]) -> Result<(usize, SockerAddress)> { - todo!() - } - - pub async fn bind(&self, _address: SockerAddress, _protocol: Protocol) -> Result<()> { - todo!() - } - - pub async fn connect(&self, _address: SockerAddress) -> Result<()> { - todo!() - } - - pub async fn accept(&self) -> Result> { - todo!() - } - - pub async fn set_send_timeout(&self, _timeout: Duration) -> Result<()> { - todo!() - } - - pub async fn set_receive_timeout(&self, _timeout: Duration) -> Result<()> { - todo!() - } - - pub async fn get_send_timeout(&self) -> Result> { - todo!() - } - - pub async fn get_receive_timeout(&self) -> Result> { - todo!() - } } diff --git a/modules/virtual_file_system/src/file_system/utilities.rs b/modules/virtual_file_system/src/file_system/utilities.rs index dd0af279..8b7efe92 100644 --- a/modules/virtual_file_system/src/file_system/utilities.rs +++ b/modules/virtual_file_system/src/file_system/utilities.rs @@ -33,7 +33,7 @@ pub(super) type CharacterDevicesMap = BTreeMap; pub(super) type BlockDevicesMap = BTreeMap; pub(super) type PipeMap = BTreeMap; -impl<'a> VirtualFileSystem<'a> { +impl VirtualFileSystem { pub(super) async fn has_permissions( users_manager: &users::Manager, current_user: UserIdentifier, @@ -77,10 +77,17 @@ impl<'a> VirtualFileSystem<'a> { let mount_point: &Path = file_system.mount_point.as_ref(); let mount_point_components = mount_point.get_components(); - let score = path_components + let striped_components = path_components .clone() - .get_common_components(mount_point_components); + .strip_prefix(&mount_point_components); + if striped_components.is_none() { + continue; + } + + let score = mount_point_components.count(); + + // Only consider this file system if all mount point components match if result_score < score { result_score = score; result = i; @@ -101,7 +108,15 @@ impl<'a> VirtualFileSystem<'a> { let relative_path = path .as_ref() .strip_prefix_absolute(&internal_file_system.mount_point) - .ok_or(Error::InvalidPath)?; + .ok_or_else(|| { + log::error!( + "Error stripping prefix {:?} from path {:?}", + internal_file_system.mount_point, + path.as_ref() + ); + + Error::InvalidPath + })?; Ok((internal_file_system, relative_path, i)) } @@ -212,7 +227,7 @@ impl<'a> VirtualFileSystem<'a> { } pub(super) async fn check_permissions_with_parent( - file_system: &dyn FileSystemOperations, + file_systems: &FileSystemsArray, path: impl AsRef, current_permission: Permission, parent_permission: Permission, @@ -221,10 +236,27 @@ impl<'a> VirtualFileSystem<'a> { if !path.as_ref().is_root() { let parent_path = path.as_ref().go_parent().ok_or(Error::InvalidPath)?; - Self::check_permissions(file_system, parent_path, parent_permission, user).await?; + let (parent_file_system, relative_path, _) = + Self::get_file_system_from_path(file_systems, &parent_path)?; // Get the file system identifier and the relative path + + Self::check_permissions( + parent_file_system.file_system, + relative_path, + parent_permission, + user, + ) + .await?; } - Self::check_permissions(file_system, path.as_ref(), current_permission, user).await?; + let (file_system, relative_path, _) = Self::get_file_system_from_path(file_systems, &path)?; // Get the file system identifier and the relative path + + Self::check_permissions( + file_system.file_system, + relative_path, + current_permission, + user, + ) + .await?; Ok(()) } diff --git a/modules/virtual_file_system/src/hierarchy.rs b/modules/virtual_file_system/src/hierarchy.rs index 767ec099..3c54ee0c 100644 --- a/modules/virtual_file_system/src/hierarchy.rs +++ b/modules/virtual_file_system/src/hierarchy.rs @@ -5,53 +5,48 @@ use task::TaskIdentifier; use crate::{Directory, Error, Result, VirtualFileSystem}; +pub fn ignore_already_exists_error(result: Result) -> Result<()> { + match result { + Ok(_) | Err(Error::AlreadyExists) => Ok(()), + Err(error) => Err(error), + } +} + /// Create the default hierarchy of the file system. pub async fn create_default_hierarchy( - virtual_file_system: &VirtualFileSystem<'_>, + virtual_file_system: &VirtualFileSystem, task: TaskIdentifier, ) -> Result<()> { virtual_file_system .set_permissions(task, &Path::ROOT, Permissions::DIRECTORY_DEFAULT) .await?; - virtual_file_system - .create_directory(task, &Path::SYSTEM) - .await?; - virtual_file_system - .create_directory(task, &Path::CONFIGURATION) - .await?; - virtual_file_system - .create_directory(task, &Path::SHARED_CONFIGURATION) - .await?; - virtual_file_system - .create_directory(task, &Path::DEVICES) - .await?; + + let paths = [ + Path::SYSTEM, + Path::CONFIGURATION, + Path::SHARED_CONFIGURATION, + Path::DEVICES, + Path::USERS, + Path::DATA, + Path::SHARED_DATA, + Path::BINARIES, + Path::TEMPORARY, + Path::LOGS, + ]; + + for path in paths { + ignore_already_exists_error(virtual_file_system.create_directory(task, &path).await)?; + } + virtual_file_system .set_permissions(task, &Path::DEVICES, Permissions::ALL_FULL) .await?; - virtual_file_system - .create_directory(task, &Path::USERS) - .await?; - virtual_file_system - .create_directory(task, &Path::DATA) - .await?; - virtual_file_system - .create_directory(task, &Path::SHARED_DATA) - .await?; - virtual_file_system - .create_directory(task, &Path::BINARIES) - .await?; - virtual_file_system - .create_directory(task, &Path::TEMPORARY) - .await?; - virtual_file_system - .create_directory(task, &Path::LOGS) - .await?; Ok(()) } -pub async fn clean_devices_in_directory<'a>( - virtual_file_system: &'a VirtualFileSystem<'a>, +pub async fn clean_devices_in_directory( + virtual_file_system: &VirtualFileSystem, task: TaskIdentifier, path: &Path, ) -> Result<()> { @@ -80,8 +75,8 @@ pub async fn clean_devices_in_directory<'a>( Ok(()) } -pub async fn clean_devices<'a>( - virtual_file_system: &'a VirtualFileSystem<'a>, +pub async fn clean_devices( + virtual_file_system: &VirtualFileSystem, task: TaskIdentifier, ) -> Result<()> { clean_devices_in_directory(virtual_file_system, task, Path::DEVICES).await?; diff --git a/modules/virtual_file_system/src/lib.rs b/modules/virtual_file_system/src/lib.rs index 8f784918..cafb0a08 100644 --- a/modules/virtual_file_system/src/lib.rs +++ b/modules/virtual_file_system/src/lib.rs @@ -10,7 +10,6 @@ mod hierarchy; mod item; mod r#macro; mod pipe; -mod socket; mod synchronous_directory; mod synchronous_file; @@ -21,7 +20,6 @@ pub use file::*; pub use file_system::*; pub use hierarchy::*; pub use item::*; -pub use socket::SockerAddress; pub use synchronous_directory::*; pub use synchronous_file::*; diff --git a/modules/virtual_file_system/src/socket/address.rs b/modules/virtual_file_system/src/socket/address.rs deleted file mode 100644 index 2312d5f6..00000000 --- a/modules/virtual_file_system/src/socket/address.rs +++ /dev/null @@ -1,45 +0,0 @@ -use file_system::PathOwned; - -use network::{IP, IPv4, IPv6, Port}; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum SockerAddress { - IPv4(IPv4, Port), - IPv6(IPv6, Port), - Local(PathOwned), -} - -impl SockerAddress { - pub fn into_ip_and_port(self) -> Option<(IP, Port)> { - match self { - Self::IPv4(ip, port) => Some((ip.into(), port)), - Self::IPv6(ip, port) => Some((ip.into(), port)), - _ => None, - } - } - - pub const fn from_ip_and_port(ip: IP, port: Port) -> Self { - match ip { - IP::IPv4(ip) => Self::IPv4(ip, port), - IP::IPv6(ip) => Self::IPv6(ip, port), - } - } -} - -impl From<(IPv4, Port)> for SockerAddress { - fn from((ip, port): (IPv4, Port)) -> Self { - Self::IPv4(ip, port) - } -} - -impl From<(IPv6, Port)> for SockerAddress { - fn from((ip, port): (IPv6, Port)) -> Self { - Self::IPv6(ip, port) - } -} - -impl From for SockerAddress { - fn from(path: PathOwned) -> Self { - Self::Local(path) - } -} diff --git a/modules/virtual_file_system/src/socket/file_system.rs b/modules/virtual_file_system/src/socket/file_system.rs deleted file mode 100644 index 177f14b7..00000000 --- a/modules/virtual_file_system/src/socket/file_system.rs +++ /dev/null @@ -1,41 +0,0 @@ -use core::{ - collections::{BTreeMap, VecDeque}, - sync::{Arc, RwLock}, -}; - -use file_system::{Inode, LocalFileIdentifier, Path_owned_type}; -use virtual_file_system::VirtualFileSystem; - -use crate::Result; - -struct Socket_type<'a> { - Data: VecDeque<&'a [u8]>, - Connection: Option<()>, -} - -pub struct Local_socket_manager_type<'a> { - Virtual_file_system: &'a VirtualFileSystem<'a>, - Open_sockets: RwLock>>>, - Sockets: RwLock>>>, -} - -impl<'a> Local_socket_manager_type<'a> { - pub fn is_socket_identifier_used( - &self, - Socket: LocalFileIdentifier, - ) -> Result { - Ok(self.Open_sockets.read().unwrap().contains_key(&Socket)) - } - - pub fn New(Virtual_file_system: &'a VirtualFileSystem<'a>) -> Self { - Self { - Virtual_file_system, - Open_sockets: RwLock::new(BTreeMap::new()), - Sockets: RwLock::new(BTreeMap::new()), - } - } - - pub fn Bind(Path: Path_owned_type, Socket: LocalFileIdentifier) -> Result<()> { - todo!() - } -} diff --git a/modules/virtual_file_system/src/socket/mod.rs b/modules/virtual_file_system/src/socket/mod.rs deleted file mode 100644 index cef9176d..00000000 --- a/modules/virtual_file_system/src/socket/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod address; - -pub use address::*; diff --git a/modules/virtual_file_system/src/synchronous_directory.rs b/modules/virtual_file_system/src/synchronous_directory.rs index 6c2d8519..772d041b 100644 --- a/modules/virtual_file_system/src/synchronous_directory.rs +++ b/modules/virtual_file_system/src/synchronous_directory.rs @@ -34,16 +34,16 @@ impl SynchronousDirectory { blocking_operation(self.flags, || operation(self)) } - pub fn create<'a>( - virtual_file_system: &'a VirtualFileSystem<'a>, + pub fn create( + virtual_file_system: &VirtualFileSystem, task: TaskIdentifier, path: impl AsRef, ) -> Result<()> { block_on(virtual_file_system.create_directory(task, &path)) } - pub fn open<'a>( - virtual_file_system: &'a VirtualFileSystem<'a>, + pub fn open( + virtual_file_system: &VirtualFileSystem, task: TaskIdentifier, path: impl AsRef, ) -> Result { @@ -114,16 +114,13 @@ impl SynchronousDirectory { Ok(self.flags.get_access()) } - pub fn close_internal<'a>( - &mut self, - virtual_file_system: &'a VirtualFileSystem<'a>, - ) -> Result<()> { + pub fn close_internal(&mut self, virtual_file_system: &VirtualFileSystem) -> Result<()> { block_on( virtual_file_system.close(&ItemStatic::Directory(self.directory), &mut self.context), ) } - pub fn close(mut self, virtual_file_system: &VirtualFileSystem<'_>) -> Result<()> { + pub fn close(mut self, virtual_file_system: &VirtualFileSystem) -> Result<()> { let result = self.close_internal(virtual_file_system); forget(self); diff --git a/modules/virtual_file_system/src/synchronous_file.rs b/modules/virtual_file_system/src/synchronous_file.rs index be225902..d3cebf8b 100644 --- a/modules/virtual_file_system/src/synchronous_file.rs +++ b/modules/virtual_file_system/src/synchronous_file.rs @@ -43,8 +43,8 @@ impl SynchronousFile { } } - pub fn open<'a>( - virtual_file_system: &'a VirtualFileSystem<'a>, + pub fn open( + virtual_file_system: &VirtualFileSystem, task: TaskIdentifier, path: impl AsRef, flags: Flags, @@ -249,14 +249,11 @@ impl SynchronousFile { Ok(self.flags.get_access()) } - pub fn close_internal( - &mut self, - virtual_file_system: &VirtualFileSystem<'_>, - ) -> crate::Result<()> { + pub fn close_internal(&mut self, virtual_file_system: &VirtualFileSystem) -> crate::Result<()> { block_on(virtual_file_system.close(&self.item, &mut self.context)) } - pub fn close(mut self, virtual_file_system: &VirtualFileSystem<'_>) -> crate::Result<()> { + pub fn close(mut self, virtual_file_system: &VirtualFileSystem) -> crate::Result<()> { let result = self.close_internal(virtual_file_system); forget(self); diff --git a/modules/virtual_file_system/tests/integration.rs b/modules/virtual_file_system/tests/integration.rs index 417e6cf6..45c036b2 100644 --- a/modules/virtual_file_system/tests/integration.rs +++ b/modules/virtual_file_system/tests/integration.rs @@ -11,7 +11,7 @@ use virtual_file_system::{File, VirtualFileSystem}; drivers_std::instantiate_global_allocator!(); -async fn initialize<'a>() -> (TaskIdentifier, &'a VirtualFileSystem<'a>) { +async fn initialize<'a>() -> (TaskIdentifier, &'a VirtualFileSystem) { let task_instance = task::initialize(); let users_manager = users::initialize(); @@ -31,14 +31,9 @@ async fn initialize<'a>() -> (TaskIdentifier, &'a VirtualFileSystem<'a>) { little_fs::FileSystem::format(device, cache_size).unwrap(); let file_system = little_fs::FileSystem::new(device, cache_size).unwrap(); - let virtual_file_system = virtual_file_system::initialize( - task_instance, - users_manager, - time_manager, - file_system, - None, - ) - .unwrap(); + let virtual_file_system = + virtual_file_system::initialize(task_instance, users_manager, time_manager, file_system) + .unwrap(); (task, virtual_file_system) } diff --git a/modules/virtual_machine/tests/test.rs b/modules/virtual_machine/tests/test.rs index e873cc7e..42657fa1 100644 --- a/modules/virtual_machine/tests/test.rs +++ b/modules/virtual_machine/tests/test.rs @@ -31,7 +31,8 @@ const FUNCTIONS: [FunctionDescriptor; 0] = Function_descriptors! {}; #[ignore] #[test] async fn integration_test() { - let (standard_in, standard_out, standard_error) = testing::initialize(false).await.split(); + let (standard_in, standard_out, standard_error) = + testing::initialize(false, false).await.split(); let task_instance = task::get_instance(); let task = task_instance.get_current_task_identifier().await; diff --git a/modules/virtual_machine/tests/test_2.rs b/modules/virtual_machine/tests/test_2.rs index 6cbf90c5..33d0fc74 100644 --- a/modules/virtual_machine/tests/test_2.rs +++ b/modules/virtual_machine/tests/test_2.rs @@ -26,7 +26,7 @@ const FUNCTIONS: [FunctionDescriptor; 0] = Function_descriptors! {}; #[ignore] #[test] async fn integration_test_2() { - let standard = testing::initialize(false).await.split(); + let standard = testing::initialize(false, false).await.split(); let task_instance = task::get_instance(); let task = task_instance.get_current_task_identifier().await; diff --git a/modules/virtual_machine/tests/test_3.rs b/modules/virtual_machine/tests/test_3.rs index 53404df9..ed7b78ba 100644 --- a/modules/virtual_machine/tests/test_3.rs +++ b/modules/virtual_machine/tests/test_3.rs @@ -32,7 +32,8 @@ const FUNCTIONS: [FunctionDescriptor; 0] = Function_descriptors! {}; #[ignore] #[test] async fn integration_test() { - let (standard_in, standard_out, standard_error) = testing::initialize(false).await.split(); + let (standard_in, standard_out, standard_error) = + testing::initialize(false, false).await.split(); let task_instance = task::get_instance(); let task = task_instance.get_current_task_identifier().await; diff --git a/modules/virtual_machine/tests/wasm_test/src/main.rs b/modules/virtual_machine/tests/wasm_test/src/main.rs index a5419a89..d9117c64 100644 --- a/modules/virtual_machine/tests/wasm_test/src/main.rs +++ b/modules/virtual_machine/tests/wasm_test/src/main.rs @@ -91,6 +91,7 @@ fn test_directory() { { let mut file = OpenOptions::new() .write(true) + .truncate(true) .create(true) .open("/test_dir/file1.txt") .unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 85d02715..10db419b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,8 @@ pub use log; #[cfg(feature = "host")] pub use memory; #[cfg(feature = "host")] +pub use network; +#[cfg(feature = "host")] pub use shared; #[cfg(feature = "host")] pub use synchronization;