Add 'lib/abi/' from commit 'fdb3e18b598f9250f9f5a6443390e0aac1f57071'
git-subtree-dir: lib/abi git-subtree-mainline: 18fa8b954a2b9372920035b0d4cdcf0d2d5c0902 git-subtree-split: fdb3e18b598f9250f9f5a6443390e0aac1f57071
This commit is contained in:
commit
22e2a992dd
1
lib/abi/.gitignore
vendored
Normal file
1
lib/abi/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
34
lib/abi/Cargo.toml
Normal file
34
lib/abi/Cargo.toml
Normal file
@ -0,0 +1,34 @@
|
||||
[package]
|
||||
name = "yggdrasil-abi"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Mark Poliakov <mark@alnyan.me>"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
core = { version = "1.0.0", optional = true, package = "rustc-std-workspace-core" }
|
||||
rustc_std_alloc = { version = "1.0.0", optional = true, package = "rustc-std-workspace-alloc" }
|
||||
compiler_builtins = { version = "0.1", optional = true }
|
||||
|
||||
serde = { version = "1.0.193", features = ["derive"], default-features = false, optional = true }
|
||||
bytemuck = { version = "1.14.0", features = ["derive"], optional = true }
|
||||
|
||||
abi-lib = { git = "https://git.alnyan.me/yggdrasil/abi-generator.git" }
|
||||
|
||||
[build-dependencies]
|
||||
yggdrasil-abi-def = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi-def.git" }
|
||||
abi-generator = { git = "https://git.alnyan.me/yggdrasil/abi-generator.git" }
|
||||
prettyplease = "0.2.15"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
alloc = []
|
||||
abi-private = []
|
||||
serde_kernel = ["serde", "serde/alloc"]
|
||||
rustc-dep-of-std = [
|
||||
"core",
|
||||
"rustc_std_alloc",
|
||||
"compiler_builtins/rustc-dep-of-std",
|
||||
"abi-lib/rustc-dep-of-std"
|
||||
]
|
32
lib/abi/build.rs
Normal file
32
lib/abi/build.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use std::{fs, path::Path};
|
||||
|
||||
use abi_generator::{
|
||||
abi::{ty::TypeWidth, AbiBuilder},
|
||||
syntax::UnwrapFancy,
|
||||
TargetEnv,
|
||||
};
|
||||
|
||||
fn generate_abi() {
|
||||
let output_dir = std::env::var("OUT_DIR").expect("$OUT_DIR not set");
|
||||
let generated_types = Path::new(&output_dir).join("generated_types.rs");
|
||||
|
||||
let abi = AbiBuilder::from_string(
|
||||
yggdrasil_abi_def::ABI_FILE,
|
||||
TargetEnv {
|
||||
thin_pointer_width: TypeWidth::U64,
|
||||
fat_pointer_width: TypeWidth::U64,
|
||||
},
|
||||
)
|
||||
.unwrap_fancy("Could not parse/read ABI file");
|
||||
|
||||
let types = prettyplease::unparse(
|
||||
&abi.emit_file(true, false)
|
||||
.unwrap_fancy("Could not emit types"),
|
||||
);
|
||||
|
||||
fs::write(generated_types, types).unwrap();
|
||||
}
|
||||
|
||||
fn main() {
|
||||
generate_abi();
|
||||
}
|
9
lib/abi/src/arch/aarch64.rs
Normal file
9
lib/abi/src/arch/aarch64.rs
Normal file
@ -0,0 +1,9 @@
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct SavedFrame {
|
||||
pub gp_regs: [u64; 32],
|
||||
pub spsr_el1: u64,
|
||||
pub elr_el1: u64,
|
||||
pub sp_el0: u64,
|
||||
}
|
13
lib/abi/src/arch/mod.rs
Normal file
13
lib/abi/src/arch/mod.rs
Normal file
@ -0,0 +1,13 @@
|
||||
//! Architecture-specific ABI implementation details
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub(crate) mod aarch64;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use aarch64 as arch_impl;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub(crate) mod x86_64;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use x86_64 as arch_impl;
|
||||
|
||||
pub use arch_impl::SavedFrame;
|
26
lib/abi/src/arch/x86_64.rs
Normal file
26
lib/abi/src/arch/x86_64.rs
Normal file
@ -0,0 +1,26 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct SavedFrame {
|
||||
pub rax: u64,
|
||||
pub rcx: u64,
|
||||
pub rdx: u64,
|
||||
pub rbx: u64,
|
||||
pub rsi: u64,
|
||||
pub rdi: u64,
|
||||
pub rbp: u64,
|
||||
|
||||
pub r8: u64,
|
||||
pub r9: u64,
|
||||
pub r10: u64,
|
||||
pub r11: u64,
|
||||
pub r12: u64,
|
||||
pub r13: u64,
|
||||
pub r14: u64,
|
||||
pub r15: u64,
|
||||
|
||||
pub user_ip: u64,
|
||||
pub user_sp: u64,
|
||||
pub rflags: u64,
|
||||
}
|
175
lib/abi/src/error.rs
Normal file
175
lib/abi/src/error.rs
Normal file
@ -0,0 +1,175 @@
|
||||
//! Error definitions and conversion functions
|
||||
use crate::{io::RawFd, primitive_enum};
|
||||
|
||||
primitive_enum!(
|
||||
#[doc = "Describes an error reported by the kernel"]
|
||||
pub enum Error: u32 {
|
||||
#[doc = "Kernel ran out of virtual address space or physical memory"]
|
||||
OutOfMemory = 1,
|
||||
#[doc = "Attempted to perform an illegal memory operation"]
|
||||
InvalidMemoryOperation = 2,
|
||||
#[doc = "Entry/file already exists"]
|
||||
AlreadyExists = 3,
|
||||
#[doc = "Operation timed out"]
|
||||
TimedOut = 4,
|
||||
#[doc = "Invalid argument supplied to a kernel function"]
|
||||
InvalidArgument = 5,
|
||||
#[doc = "Entry/file does not exist"]
|
||||
DoesNotExist = 6,
|
||||
#[doc = "A directory found where something else was expected"]
|
||||
IsADirectory = 7,
|
||||
#[doc = "Invalid file descriptor supplied to a kernel function"]
|
||||
InvalidFile = 8,
|
||||
#[doc = "Kernel/driver function is not implemented"]
|
||||
NotImplemented = 9,
|
||||
#[doc = "Entry is not a directory"]
|
||||
NotADirectory = 10,
|
||||
#[doc = "Directory is not empty"]
|
||||
DirectoryNotEmpty = 11,
|
||||
#[doc = "Operation was interrupted"]
|
||||
Interrupted = 12,
|
||||
#[doc = "Operation has not completed yet"]
|
||||
WouldBlock = 13,
|
||||
#[doc = "Entry/file cannot be written to"]
|
||||
ReadOnly = 14,
|
||||
#[doc = "Requested operation cannot be performed or is invalid"]
|
||||
InvalidOperation = 15,
|
||||
#[doc = "No permission to access the entry/file"]
|
||||
PermissionDenied = 16,
|
||||
#[doc = "Unrecognized or invalid executable file format"]
|
||||
UnrecognizedExecutable = 17,
|
||||
#[doc = "Data received was less than expected"]
|
||||
MissingData = 18,
|
||||
#[doc = "Too many data received"]
|
||||
QueueFull = 19,
|
||||
#[doc = "Provided buffer was too small"]
|
||||
BufferTooSmall = 20,
|
||||
|
||||
// Networking errors
|
||||
#[doc = "No route to host"]
|
||||
HostUnreachable = 128,
|
||||
#[doc = "Network unreachable"]
|
||||
NetworkUnreachable = 129,
|
||||
#[doc = "Connection reset"]
|
||||
ConnectionReset = 130,
|
||||
#[doc = "Address already used"]
|
||||
AddrInUse = 131,
|
||||
#[doc = "Connection refused"]
|
||||
ConnectionRefused = 132,
|
||||
}
|
||||
);
|
||||
|
||||
/// Interface for converting between system call result representations
|
||||
pub trait SyscallResult: Sized {
|
||||
/// Converts raw value into a system call result
|
||||
fn from_syscall_result(value: usize) -> Result<Self, Error>;
|
||||
|
||||
/// Converts system call result into a raw value
|
||||
fn into_syscall_result(self) -> usize;
|
||||
}
|
||||
|
||||
/// Interface for converting between system call error representations
|
||||
pub trait SyscallError {
|
||||
/// Converts raw value into a system call error
|
||||
fn from_syscall_error(value: usize) -> Self;
|
||||
|
||||
/// Converts system call error into a raw value
|
||||
fn into_syscall_error(self) -> usize;
|
||||
}
|
||||
|
||||
impl SyscallError for Error {
|
||||
fn from_syscall_error(value: usize) -> Self {
|
||||
Error::try_from((-(value as isize)) as u32).unwrap_or(Error::InvalidArgument)
|
||||
}
|
||||
|
||||
fn into_syscall_error(self) -> usize {
|
||||
(-((self as u32) as isize)) as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SyscallResult> SyscallResult for Result<T, Error> {
|
||||
fn into_syscall_result(self) -> usize {
|
||||
match self {
|
||||
Ok(t) => t.into_syscall_result(),
|
||||
Err(e) => e.into_syscall_error(),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_syscall_result(_value: usize) -> Result<Self, Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl SyscallResult for () {
|
||||
fn from_syscall_result(value: usize) -> Result<Self, Error> {
|
||||
if (value as isize) < 0 {
|
||||
Err(Error::from_syscall_error(value))
|
||||
} else {
|
||||
// TODO assert value == 0
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn into_syscall_result(self) -> usize {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl SyscallResult for usize {
|
||||
fn from_syscall_result(value: usize) -> Result<Self, Error> {
|
||||
if (value as isize) < 0 {
|
||||
Err(Error::from_syscall_error(value))
|
||||
} else {
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
fn into_syscall_result(self) -> usize {
|
||||
assert!((self as isize) >= 0);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl SyscallResult for RawFd {
|
||||
fn from_syscall_result(value: usize) -> Result<Self, Error> {
|
||||
if (value as isize) < 0 {
|
||||
Err(Error::from_syscall_error(value))
|
||||
} else {
|
||||
// TODO assert value < u32::MAX
|
||||
Ok(RawFd(value as u32))
|
||||
}
|
||||
}
|
||||
|
||||
fn into_syscall_result(self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl SyscallResult for u64 {
|
||||
fn from_syscall_result(value: usize) -> Result<Self, Error> {
|
||||
if (value as isize) < 0 {
|
||||
Err(Error::from_syscall_error(value))
|
||||
} else {
|
||||
Ok(value as _)
|
||||
}
|
||||
}
|
||||
|
||||
fn into_syscall_result(self) -> usize {
|
||||
assert_eq!(self >> 63, 0);
|
||||
self as _
|
||||
}
|
||||
}
|
||||
|
||||
impl SyscallResult for u32 {
|
||||
fn from_syscall_result(value: usize) -> Result<Self, Error> {
|
||||
if (value as isize) < 0 {
|
||||
Err(Error::from_syscall_error(value))
|
||||
} else {
|
||||
Ok(value as _)
|
||||
}
|
||||
}
|
||||
|
||||
fn into_syscall_result(self) -> usize {
|
||||
self as _
|
||||
}
|
||||
}
|
60
lib/abi/src/io/channel.rs
Normal file
60
lib/abi/src/io/channel.rs
Normal file
@ -0,0 +1,60 @@
|
||||
use abi_lib::SyscallRegister;
|
||||
|
||||
pub use crate::generated::ChannelPublisherId;
|
||||
|
||||
use super::RawFd;
|
||||
|
||||
/// Describes a message sent using SendMessage
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SentMessage<'m> {
|
||||
/// A file is sent
|
||||
File(RawFd),
|
||||
/// Binary data is sent
|
||||
Data(&'m [u8]),
|
||||
}
|
||||
|
||||
/// Describes a message received using ReceiveMessage
|
||||
#[derive(Debug)]
|
||||
pub enum ReceivedMessageMetadata {
|
||||
/// A file was received
|
||||
File(RawFd),
|
||||
/// Binary data was received (contains data length)
|
||||
Data(usize),
|
||||
}
|
||||
|
||||
/// Specifies where a message should be delivered on a channel
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum MessageDestination {
|
||||
/// Broadcast everywhere, including the sender.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// May cause a feedback loop if the sender is also a subscriber of the channel.
|
||||
All,
|
||||
/// Broadcast everywhere, except the sender
|
||||
AllExceptSelf,
|
||||
/// Send to a specific subscriber ID
|
||||
Specific(u32),
|
||||
}
|
||||
|
||||
impl SyscallRegister for MessageDestination {
|
||||
fn from_syscall_register(value: usize) -> Self {
|
||||
match value {
|
||||
0 => Self::All,
|
||||
1 => Self::AllExceptSelf,
|
||||
n => Self::Specific((n - 2) as _),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_syscall_register(self) -> usize {
|
||||
match self {
|
||||
Self::All => 0,
|
||||
Self::AllExceptSelf => 1,
|
||||
Self::Specific(v) => (v + 2) as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ChannelPublisherId {
|
||||
pub const ZERO: Self = Self(0);
|
||||
}
|
47
lib/abi/src/io/device.rs
Normal file
47
lib/abi/src/io/device.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use core::mem::MaybeUninit;
|
||||
|
||||
use crate::generated::ProcessId;
|
||||
|
||||
use super::terminal;
|
||||
|
||||
/// Describes how a mount operation should be performed
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct MountOptions<'a> {
|
||||
/// Block device to use as a mount source. May be empty when mounting virtual filesystems.
|
||||
pub source: Option<&'a str>,
|
||||
/// Filesystem to use when mounting. Empty means "deduce automatically" and source must be set.
|
||||
pub filesystem: Option<&'a str>,
|
||||
/// Path to a directory to mount the filesystem to
|
||||
pub target: &'a str,
|
||||
}
|
||||
|
||||
/// Describes how an unmount operation should be performed
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct UnmountOptions {
|
||||
/// Path to the mountpoint to unmount
|
||||
pub mountpoint: &'static str,
|
||||
}
|
||||
|
||||
// TODO make this accept references when "reading" something
|
||||
/// Describes device-specific requests
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
#[non_exhaustive]
|
||||
pub enum DeviceRequest {
|
||||
/// Configure the terminal's options
|
||||
SetTerminalOptions(terminal::TerminalOptions),
|
||||
/// Read the terminal's options
|
||||
GetTerminalOptions(MaybeUninit<terminal::TerminalOptions>),
|
||||
/// Set a new terminal size
|
||||
SetTerminalSize(terminal::TerminalSize),
|
||||
/// Return the terminal's size
|
||||
GetTerminalSize(MaybeUninit<terminal::TerminalSize>),
|
||||
/// Sets a foreground process group ID for the terminal
|
||||
SetTerminalGroup(ProcessId),
|
||||
/// "Acquires" ownership of the device, preventing others from accessing it
|
||||
AcquireDevice,
|
||||
/// "Releases" ownership of the device
|
||||
ReleaseDevice,
|
||||
}
|
103
lib/abi/src/io/file.rs
Normal file
103
lib/abi/src/io/file.rs
Normal file
@ -0,0 +1,103 @@
|
||||
use core::fmt;
|
||||
|
||||
use abi_lib::SyscallRegister;
|
||||
|
||||
use super::{FileMode, FileType, GroupId, UserId};
|
||||
|
||||
/// Describes an operation to perform when updating certain file metadata elements
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FileMetadataUpdateMode {
|
||||
/// value = new
|
||||
Set,
|
||||
/// value = old | new
|
||||
Or,
|
||||
/// value = old & new
|
||||
And,
|
||||
/// value = old & !new
|
||||
AndNot,
|
||||
}
|
||||
|
||||
/// Describes a modification to a file's metadata
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FileMetadataUpdate {
|
||||
/// Changes file permissions
|
||||
Permissions(FileMode, FileMetadataUpdateMode),
|
||||
}
|
||||
|
||||
/// Describes metadata associated with a filesystem entry
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct FileAttr {
|
||||
/// Data size for files, UNSPECIFIED for the rest
|
||||
pub size: u64,
|
||||
/// Entry type
|
||||
pub ty: FileType,
|
||||
/// Entry access permissions
|
||||
pub mode: FileMode,
|
||||
|
||||
/// Owner user ID
|
||||
pub uid: UserId,
|
||||
/// Owner group ID
|
||||
pub gid: GroupId,
|
||||
}
|
||||
|
||||
/// Describes a seek operation for a regular file
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum SeekFrom {
|
||||
/// Argument is an offset from the start of the file
|
||||
Start(u64),
|
||||
/// Argument is an offset from the end of the file
|
||||
End(i64),
|
||||
/// Argument is an offset from the current position
|
||||
Current(i64),
|
||||
}
|
||||
|
||||
impl SyscallRegister for SeekFrom {
|
||||
fn into_syscall_register(self) -> usize {
|
||||
match self {
|
||||
SeekFrom::Start(value) => (value as usize) << 2,
|
||||
SeekFrom::End(value) => (value << 2) as usize | 1,
|
||||
SeekFrom::Current(value) => (value << 2) as usize | 2,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_syscall_register(value: usize) -> Self {
|
||||
let dir = value & 0x3;
|
||||
|
||||
match dir {
|
||||
0 => Self::Start((value as u64) >> 2),
|
||||
1 => Self::End((value as i64) >> 2),
|
||||
2 => Self::Current((value as i64) >> 2),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FileMode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
macro_rules! print_bit {
|
||||
($res:expr, $val:expr, $self:expr, $bit:expr) => {
|
||||
if $self.contains($bit) {
|
||||
$res = $val;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let mut buf = ['-'; 9];
|
||||
|
||||
print_bit!(buf[0], 'r', self, Self::USER_READ);
|
||||
print_bit!(buf[1], 'w', self, Self::USER_WRITE);
|
||||
print_bit!(buf[2], 'x', self, Self::USER_EXEC);
|
||||
print_bit!(buf[3], 'r', self, Self::GROUP_READ);
|
||||
print_bit!(buf[4], 'w', self, Self::GROUP_WRITE);
|
||||
print_bit!(buf[5], 'x', self, Self::GROUP_EXEC);
|
||||
print_bit!(buf[6], 'r', self, Self::OTHER_READ);
|
||||
print_bit!(buf[7], 'w', self, Self::OTHER_WRITE);
|
||||
print_bit!(buf[8], 'x', self, Self::OTHER_EXEC);
|
||||
|
||||
for ch in buf.iter() {
|
||||
fmt::Display::fmt(ch, f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
134
lib/abi/src/io/input.rs
Normal file
134
lib/abi/src/io/input.rs
Normal file
@ -0,0 +1,134 @@
|
||||
/// Describes a key pressed/released on a keyboard device
|
||||
// Missing docs: self-explanatory names
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[repr(C)]
|
||||
pub enum KeyboardKey {
|
||||
Char(u8),
|
||||
F(u8),
|
||||
Enter,
|
||||
LShift,
|
||||
RShift,
|
||||
LControl,
|
||||
RControl,
|
||||
LAlt,
|
||||
RAlt,
|
||||
CapsLock,
|
||||
Escape,
|
||||
Backspace,
|
||||
Tab,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// Alternative representation for [KeyboardKey]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[repr(transparent)]
|
||||
pub struct KeyboardKeyCode(u16);
|
||||
|
||||
/// Descibes a single event produced by a keyboard device
|
||||
// Missing docs: self-explanatory names
|
||||
#[allow(missing_docs)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum KeyboardKeyEvent {
|
||||
Pressed(KeyboardKey),
|
||||
Released(KeyboardKey),
|
||||
}
|
||||
|
||||
impl KeyboardKey {
|
||||
/// Converts [KeyboardKey] to its related [KeyboardKeyCode]
|
||||
pub const fn code(self) -> KeyboardKeyCode {
|
||||
// KeyCode:
|
||||
// 0x00..0x80: chars
|
||||
// 0x80..0x8C: f1..f12
|
||||
// 0x90: Enter
|
||||
// 0x91: LShift
|
||||
// 0x92: RShift
|
||||
// 0x93: LControl
|
||||
// 0x94: RControl
|
||||
// 0x95: LAlt
|
||||
// 0x96: RAlt
|
||||
// 0x97: CapsLock
|
||||
// 0x98: Escape
|
||||
// 0x99: Backspace
|
||||
// 0x9A: Tab
|
||||
// 0xFF: Unknown
|
||||
KeyboardKeyCode(match self {
|
||||
Self::Char(b) => b as u16,
|
||||
Self::F(b) => (b - 1) as u16 | 0x80,
|
||||
Self::Enter => 0x90,
|
||||
Self::LShift => 0x91,
|
||||
Self::RShift => 0x92,
|
||||
Self::LControl => 0x93,
|
||||
Self::RControl => 0x94,
|
||||
Self::LAlt => 0x95,
|
||||
Self::RAlt => 0x96,
|
||||
Self::CapsLock => 0x97,
|
||||
Self::Escape => 0x98,
|
||||
Self::Backspace => 0x99,
|
||||
Self::Tab => 0x9A,
|
||||
Self::Unknown => 0xFF,
|
||||
})
|
||||
}
|
||||
|
||||
/// Converts a [KeyboardKeyCode] to the [KeyboardKey] it represents. Unknown keycodes are
|
||||
/// replaced with [KeyboardKey::Unknown].
|
||||
pub const fn from_code(KeyboardKeyCode(code): KeyboardKeyCode) -> Self {
|
||||
if code < 0x80 {
|
||||
Self::Char(code as u8)
|
||||
} else if code < 0x8C {
|
||||
Self::F(code as u8 - 0x79)
|
||||
} else {
|
||||
match code {
|
||||
0x90 => Self::Enter,
|
||||
0x91 => Self::LShift,
|
||||
0x92 => Self::RShift,
|
||||
0x93 => Self::LControl,
|
||||
0x94 => Self::RControl,
|
||||
0x95 => Self::LAlt,
|
||||
0x96 => Self::RAlt,
|
||||
0x97 => Self::CapsLock,
|
||||
0x98 => Self::Escape,
|
||||
0x99 => Self::Backspace,
|
||||
0x9A => Self::Tab,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyboardKeyEvent {
|
||||
/// Splits the event into its state and key
|
||||
#[inline]
|
||||
pub const fn split(self) -> (KeyboardKey, bool) {
|
||||
match self {
|
||||
Self::Pressed(k) => (k, true),
|
||||
Self::Released(k) => (k, false),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a [KeyEvent] to its byte representation
|
||||
#[inline]
|
||||
pub const fn as_bytes(self) -> [u8; 4] {
|
||||
let (key, pressed) = self.split();
|
||||
let code = key.code().0.to_ne_bytes();
|
||||
|
||||
[code[0], code[1], pressed as u8, 0]
|
||||
// (((pressed as u32) << 16) | (key.code().0 as u32)).to_ne_bytes()
|
||||
}
|
||||
|
||||
/// Obtains a [KeyEvent] from its byte representation
|
||||
pub const fn from_bytes(bytes: [u8; 4]) -> Self {
|
||||
let pressed = bytes[2] != 0;
|
||||
let code = KeyboardKeyCode(u16::from_ne_bytes([bytes[0], bytes[1]]));
|
||||
let key = KeyboardKey::from_code(code);
|
||||
|
||||
if pressed {
|
||||
Self::Pressed(key)
|
||||
} else {
|
||||
Self::Released(key)
|
||||
}
|
||||
}
|
||||
}
|
81
lib/abi/src/io/mod.rs
Normal file
81
lib/abi/src/io/mod.rs
Normal file
@ -0,0 +1,81 @@
|
||||
use crate::util::FixedString;
|
||||
|
||||
mod channel;
|
||||
mod device;
|
||||
mod file;
|
||||
mod input;
|
||||
mod terminal;
|
||||
|
||||
pub use crate::generated::{FileMode, FileType, GroupId, OpenOptions, PollControl, RawFd, UserId};
|
||||
pub use channel::{ChannelPublisherId, MessageDestination, ReceivedMessageMetadata, SentMessage};
|
||||
pub use device::{DeviceRequest, MountOptions, UnmountOptions};
|
||||
pub use file::{FileAttr, FileMetadataUpdate, FileMetadataUpdateMode, SeekFrom};
|
||||
pub use input::{KeyboardKey, KeyboardKeyCode, KeyboardKeyEvent};
|
||||
pub use terminal::{
|
||||
TerminalControlCharacters, TerminalInputOptions, TerminalLineOptions, TerminalOptions,
|
||||
TerminalOutputOptions, TerminalSize,
|
||||
};
|
||||
|
||||
impl UserId {
|
||||
pub const fn root() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
|
||||
pub const fn is_root(&self) -> bool {
|
||||
self.0 == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupId {
|
||||
pub const fn root() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
|
||||
pub const fn is_root(&self) -> bool {
|
||||
self.0 == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl FileMode {
|
||||
pub const fn new(value: u32) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
|
||||
/// Default mode for a regular file
|
||||
pub const fn default_file() -> Self {
|
||||
Self(0o666)
|
||||
}
|
||||
|
||||
/// Default mode for a directory
|
||||
pub const fn default_dir() -> Self {
|
||||
Self(0o777)
|
||||
}
|
||||
}
|
||||
|
||||
/// Raw directory entry representation
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct DirectoryEntry {
|
||||
/// Name of the entry
|
||||
pub name: FixedString<256>,
|
||||
/// Type of the entry
|
||||
pub ty: FileType,
|
||||
}
|
||||
|
||||
impl RawFd {
|
||||
pub const STDIN: Self = Self(0);
|
||||
pub const STDOUT: Self = Self(1);
|
||||
pub const STDERR: Self = Self(2);
|
||||
}
|
||||
|
||||
impl OpenOptions {
|
||||
pub const fn default() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for RawFd {
|
||||
fn from(value: u32) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
140
lib/abi/src/io/terminal.rs
Normal file
140
lib/abi/src/io/terminal.rs
Normal file
@ -0,0 +1,140 @@
|
||||
use crate::bitflags;
|
||||
|
||||
bitflags! {
|
||||
#[doc = "Defines how output to the terminal should be processed"]
|
||||
#[default = (NL_TO_CRNL)]
|
||||
pub struct TerminalOutputOptions: u32 {
|
||||
#[doc = "Translate \n to \r\n"]
|
||||
const NL_TO_CRNL: bit 0;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[doc = "Defines how input should be presented to the reader"]
|
||||
#[default = (CR_TO_NL)]
|
||||
pub struct TerminalInputOptions: u32 {
|
||||
#[doc = "Translate \n to \r on input"]
|
||||
const NL_TO_CR: bit 0;
|
||||
#[doc = "Translate \r to \n on input"]
|
||||
const CR_TO_NL: bit 1;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[doc = "Controls the line discipline of the terminal"]
|
||||
#[default = (SIGNAL | CANONICAL | ECHO | ECHO_ERASE | ECHO_KILL | ECHO_NL)]
|
||||
pub struct TerminalLineOptions: u32 {
|
||||
#[doc = "Enables signal processing for special bytes (INTR, QUIT, etc.)"]
|
||||
const SIGNAL: bit 0;
|
||||
#[doc = "Enables canonical mode"]
|
||||
const CANONICAL: bit 1;
|
||||
#[doc = "Echo input characters back"]
|
||||
const ECHO: bit 2;
|
||||
#[doc = "If CANONICAL is set, ERASE character erases chars, WORD_ERASE erases words"]
|
||||
const ECHO_ERASE: bit 3;
|
||||
#[doc = "If CANONICAL is set, KILL erases lines"]
|
||||
const ECHO_KILL: bit 4;
|
||||
#[doc = "If CANONICAL is set, echo newline even if ECHO is not set"]
|
||||
const ECHO_NL: bit 5;
|
||||
}
|
||||
}
|
||||
|
||||
/// Specifies a set of special control characters
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct TerminalControlCharacters {
|
||||
/// End-of-file character (0x04, ^D)
|
||||
pub eof: u8,
|
||||
/// Erase character (0x7F, ^?)
|
||||
pub erase: u8,
|
||||
/// Word erase character (0x17, ^W)
|
||||
pub werase: u8,
|
||||
/// Interrupt character (0x03, ^C)
|
||||
pub interrupt: u8,
|
||||
/// Kill character (0x15, ^U)
|
||||
pub kill: u8,
|
||||
}
|
||||
|
||||
/// Terminal I/O transformation and control settings
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct TerminalOptions {
|
||||
/// Controls output processing
|
||||
pub output: TerminalOutputOptions,
|
||||
/// Controls input processing
|
||||
pub input: TerminalInputOptions,
|
||||
/// Controls special bytes and line discipline
|
||||
pub line: TerminalLineOptions,
|
||||
/// Specifies control characters of the terminal
|
||||
pub chars: TerminalControlCharacters,
|
||||
}
|
||||
|
||||
/// Describes terminal size in rows and columns
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct TerminalSize {
|
||||
/// Number of text rows
|
||||
pub rows: usize,
|
||||
/// Number of text columns
|
||||
pub columns: usize,
|
||||
}
|
||||
|
||||
impl TerminalControlCharacters {
|
||||
/// const-version of [Default] trait impl
|
||||
pub const fn const_default() -> Self {
|
||||
Self {
|
||||
eof: 0x04,
|
||||
erase: 0x7F,
|
||||
interrupt: 0x03,
|
||||
kill: 0x15,
|
||||
werase: 0x17,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TerminalLineOptions {
|
||||
/// Returns the line options used for raw tty input
|
||||
pub const fn raw_input() -> Self {
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl TerminalOptions {
|
||||
/// const-version of [Default] trait impl
|
||||
pub const fn const_default() -> Self {
|
||||
Self {
|
||||
output: TerminalOutputOptions::const_default(),
|
||||
input: TerminalInputOptions::const_default(),
|
||||
line: TerminalLineOptions::const_default(),
|
||||
chars: TerminalControlCharacters::const_default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns terminal options configured for raw tty input
|
||||
pub const fn raw_input() -> Self {
|
||||
Self {
|
||||
line: TerminalLineOptions::raw_input(),
|
||||
..Self::const_default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if CANONICAL flag is set for this terminal
|
||||
pub const fn is_canonical(&self) -> bool {
|
||||
self.line.contains(TerminalLineOptions::CANONICAL)
|
||||
}
|
||||
|
||||
/// Returns `true` if the terminal echoes the '\n' character
|
||||
pub fn is_echo_newline(&self) -> bool {
|
||||
self.line.contains_any(
|
||||
TerminalLineOptions::CANONICAL
|
||||
| TerminalLineOptions::ECHO
|
||||
| TerminalLineOptions::ECHO_NL,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TerminalOptions {
|
||||
fn default() -> Self {
|
||||
Self::const_default()
|
||||
}
|
||||
}
|
40
lib/abi/src/lib.rs
Normal file
40
lib/abi/src/lib.rs
Normal file
@ -0,0 +1,40 @@
|
||||
//! Yggdrasil OS user-kernel communication ABI
|
||||
#![no_std]
|
||||
#![allow(
|
||||
clippy::new_without_default,
|
||||
clippy::should_implement_trait,
|
||||
clippy::module_inception,
|
||||
incomplete_features,
|
||||
stable_features
|
||||
)]
|
||||
#![feature(trace_macros, const_trait_impl, inline_const_pat, ip_in_core)]
|
||||
// TODO temporary
|
||||
#![allow(missing_docs)]
|
||||
|
||||
#[cfg(all(feature = "alloc", not(feature = "rustc-dep-of-std")))]
|
||||
extern crate alloc;
|
||||
#[cfg(feature = "rustc-dep-of-std")]
|
||||
extern crate rustc_std_alloc as alloc;
|
||||
|
||||
pub(crate) mod macros;
|
||||
pub mod pass;
|
||||
pub mod util;
|
||||
|
||||
mod generated {
|
||||
include!(concat!(env!("OUT_DIR"), "/generated_types.rs"));
|
||||
}
|
||||
|
||||
pub mod error {
|
||||
pub use crate::generated::Error;
|
||||
// TODO have syscall-generator implement TryFrom<#repr> for #enum
|
||||
}
|
||||
|
||||
pub use generated::SyscallFunction;
|
||||
|
||||
pub mod arch;
|
||||
pub mod io;
|
||||
pub mod mem;
|
||||
pub mod net;
|
||||
pub mod path;
|
||||
pub mod process;
|
||||
pub mod system;
|
336
lib/abi/src/macros.rs
Normal file
336
lib/abi/src/macros.rs
Normal file
@ -0,0 +1,336 @@
|
||||
/// Helper macro to define primitive enums with integer reprs, as well as their conversion methods
|
||||
#[macro_export]
|
||||
macro_rules! primitive_enum {
|
||||
($(#[$struct_meta:meta])* $vis:vis enum $name:ident: $repr:ty {
|
||||
$( $(#[$variant_meta:meta])* $variant:ident = $discriminant:literal, )+
|
||||
}) => {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
#[repr($repr)]
|
||||
$(#[$struct_meta])*
|
||||
$vis enum $name {
|
||||
$(
|
||||
$(#[$variant_meta])?
|
||||
$variant = $discriminant
|
||||
),+
|
||||
}
|
||||
|
||||
impl TryFrom<$repr> for $name {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(v: $repr) -> Result<$name, ()> {
|
||||
match v {
|
||||
$(
|
||||
$discriminant => Ok($name::$variant)
|
||||
,)+
|
||||
_ => Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$name> for $repr {
|
||||
fn from(v: $name) -> $repr {
|
||||
v as $repr
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Common implementations for bitflag definitions
|
||||
#[macro_export]
|
||||
macro_rules! bitflags_impl_common {
|
||||
($name:ident, $repr:ty) => {
|
||||
impl $name {
|
||||
/// Constructs a value from its bit representation
|
||||
pub const fn new(bits: $repr) -> Self {
|
||||
Self(bits)
|
||||
}
|
||||
|
||||
/// Constructs a value with all bits set to zero
|
||||
pub const fn empty() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
|
||||
/// Returns the bit representation of the value
|
||||
#[inline]
|
||||
pub const fn bits(self) -> $repr {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns `true` if the value contains all bits in `bits`
|
||||
#[inline]
|
||||
pub const fn contains(self, bits: $name) -> bool {
|
||||
self.0 & bits.bits() == bits.bits()
|
||||
}
|
||||
|
||||
/// Returns `true` if the value contains any of the bits in `bits`
|
||||
#[inline]
|
||||
pub const fn contains_any(self, bits: $name) -> bool {
|
||||
self.0 & bits.bits() != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$repr> for $name {
|
||||
fn from(value: $repr) -> $name {
|
||||
$name::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$name> for $repr {
|
||||
fn from(value: $name) -> $repr {
|
||||
value.bits()
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::BitOr for $name {
|
||||
type Output = $name;
|
||||
|
||||
fn bitor(self, rhs: $name) -> Self::Output {
|
||||
Self::new(self.bits() | rhs.bits())
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::BitAnd for $name {
|
||||
type Output = $name;
|
||||
|
||||
fn bitand(self, rhs: $name) -> Self::Output {
|
||||
Self::new(self.bits() & rhs.bits())
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::BitOrAssign for $name {
|
||||
fn bitor_assign(&mut self, rhs: $name) {
|
||||
self.0 |= rhs.bits();
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::BitAndAssign for $name {
|
||||
fn bitand_assign(&mut self, rhs: $name) {
|
||||
self.0 &= rhs.bits();
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Not for $name {
|
||||
type Output = $name;
|
||||
|
||||
fn not(self) -> Self::Output {
|
||||
Self::new(!self.bits())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Implements Default trait for a bitflag definition
|
||||
#[macro_export]
|
||||
macro_rules! bitflags_impl_default {
|
||||
($name:ident, $repr:ty) => {};
|
||||
|
||||
($name:ident, $repr:ty, ($($flag:ident)|+)) => {
|
||||
impl Default for $name {
|
||||
fn default() -> Self {
|
||||
$name($($name::$flag.bits())|+)
|
||||
}
|
||||
}
|
||||
|
||||
impl $name {
|
||||
/// const-version of [Default] trait impl
|
||||
pub const fn const_default() -> Self {
|
||||
Self($($name::$flag.bits())|+)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
($name:ident, $repr:ty, ()) => {
|
||||
impl Default for $name {
|
||||
fn default() -> Self {
|
||||
$name(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl $name {
|
||||
/// const-version of [Default] trait impl
|
||||
pub const fn const_default() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Helper macro to construct bit flag wrappers
|
||||
#[macro_export]
|
||||
macro_rules! bitflags {
|
||||
(
|
||||
$(#[doc = $struct_doc:expr])?
|
||||
$(#[default = $struct_default:tt])?
|
||||
$vis:vis struct $name:ident: $repr:ty {
|
||||
$(
|
||||
$(#[doc = $field_doc:expr])?
|
||||
const $field_name:ident: bit $field_offset:expr;
|
||||
)+
|
||||
}
|
||||
) => {
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[repr(transparent)]
|
||||
$(#[doc = $struct_doc])?
|
||||
$vis struct $name($repr);
|
||||
|
||||
impl $name {
|
||||
$(
|
||||
$(#[doc = $field_doc])?
|
||||
pub const $field_name: $name = $name(1 << $field_offset);
|
||||
)+
|
||||
}
|
||||
|
||||
$crate::bitflags_impl_common!($name, $repr);
|
||||
|
||||
impl core::fmt::Debug for $name {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
let mut s = f.debug_struct(stringify!($name));
|
||||
$(
|
||||
s.field(stringify!($field_name), &self.contains($name::$field_name));
|
||||
)+
|
||||
s.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
$crate::bitflags_impl_default!($name, $repr$(, $struct_default)?);
|
||||
};
|
||||
}
|
||||
|
||||
/// Generates a #[repr(transparent)] wrapper for a value with Debug, Copy, Eq, Ord, Hash derived +
|
||||
/// Into<T> implemented
|
||||
#[macro_export]
|
||||
macro_rules! transparent_wrapper {
|
||||
($(#[$struct_meta:meta])* $vis:vis struct $name:ident ( $ty:ty )) => {
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
|
||||
$(#[$struct_meta])* $vis struct $name ($ty);
|
||||
|
||||
impl $name {
|
||||
/// Constructs the value from its raw representation.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe: the actual subset of values represented by this type may not include all
|
||||
/// the raw values.
|
||||
pub unsafe fn from_raw(value: $ty) -> $name {
|
||||
$name(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$name> for $ty {
|
||||
fn from(value: $name) -> $ty {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[macro_export]
|
||||
macro_rules! define_bitfield_getter {
|
||||
($offset:literal => $getter:ident: $ty:ty) => {
|
||||
pub fn $getter(&self) -> bool {
|
||||
unsafe { core::ptr::read_volatile(&self.0) & (1 << $offset) != 0 }
|
||||
}
|
||||
};
|
||||
(($start:literal..$end:literal) => $getter:ident: $ty:ty) => {
|
||||
pub fn $getter(&self) -> $ty {
|
||||
(unsafe { core::ptr::read_volatile(&self.0) } >> $start) & ((1 << ($end - $start)) - 1)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[macro_export]
|
||||
macro_rules! define_bitfield_setter {
|
||||
($offset:literal => $setter:ident: $ty:ty) => {
|
||||
pub fn $setter(&mut self, value: bool) -> &mut Self {
|
||||
// TODO volatile
|
||||
if value {
|
||||
self.0 |= 1 << $offset;
|
||||
} else {
|
||||
self.0 &= !(1 << $offset);
|
||||
}
|
||||
self
|
||||
}
|
||||
};
|
||||
(($start:literal..$end:literal) => $setter:ident: $ty:ty) => {
|
||||
pub fn $setter(&mut self, value: $ty) -> &mut Self {
|
||||
const MASK: $ty = ((1 << ($end - $start)) - 1) << $start;
|
||||
self.0 &= !MASK;
|
||||
self.0 |= (value << $start) & MASK;
|
||||
self
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[macro_export]
|
||||
macro_rules! define_bitfield_argument {
|
||||
($offset:literal => $ty:ty) => {
|
||||
bool
|
||||
};
|
||||
(($start:literal..$end:literal) => $ty:ty) => {
|
||||
$ty
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[macro_export]
|
||||
macro_rules! define_bitfield_constructor_field {
|
||||
($self:ident $offset:literal => $getter:ident) => {
|
||||
if $getter {
|
||||
$self.0 |= 1 << $offset;
|
||||
}
|
||||
};
|
||||
($self:ident ($start:literal..$end:literal) => $getter:ident) => {
|
||||
$self.0 |= ($getter & ((1 << ($end - $start)) - 1)) << $start;
|
||||
};
|
||||
}
|
||||
|
||||
/// Helper macro to declare more complex bitfields than [bitflags!] does.
|
||||
/// TODO: deprecate and remove [bitflags!].
|
||||
#[macro_export]
|
||||
macro_rules! define_bitfields {
|
||||
($vis:vis $name:ident : $ty:ty {
|
||||
$($range:tt => $getter:ident $(+ $setter:ident)?),* $(,)*
|
||||
}) => {
|
||||
#[derive(Clone, Copy, PartialEq, Eq, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
#[repr(transparent)]
|
||||
$vis struct $name($ty);
|
||||
|
||||
impl $name {
|
||||
pub const fn new(
|
||||
$( $getter: yggdrasil_abi::define_bitfield_argument!($range => $ty), )*
|
||||
) -> Self {
|
||||
let mut this = Self(0);
|
||||
$(
|
||||
yggdrasil_abi::define_bitfield_constructor_field!(this $range => $getter);
|
||||
)*
|
||||
this
|
||||
}
|
||||
|
||||
pub const fn into_raw(self) -> $ty {
|
||||
self.0
|
||||
}
|
||||
|
||||
$(
|
||||
yggdrasil_abi::define_bitfield_getter!($range => $getter:$ty);
|
||||
|
||||
$(
|
||||
yggdrasil_abi::define_bitfield_setter!($range => $setter:$ty);
|
||||
)?
|
||||
)*
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for $name {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct(stringify!($name))
|
||||
$(
|
||||
.field(stringify!($getter), &self.$getter())
|
||||
)*
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
12
lib/abi/src/mem.rs
Normal file
12
lib/abi/src/mem.rs
Normal file
@ -0,0 +1,12 @@
|
||||
//! Memory-related system functions and types
|
||||
|
||||
use crate::io::RawFd;
|
||||
|
||||
/// Defines the MapMemory function
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum MappingSource {
|
||||
/// Mapping is backed by regular memory pages
|
||||
Anonymous,
|
||||
/// Mapping is backed by a file
|
||||
File(RawFd, u64),
|
||||
}
|
551
lib/abi/src/net/dns.rs
Normal file
551
lib/abi/src/net/dns.rs
Normal file
@ -0,0 +1,551 @@
|
||||
//! DNS-related protocol structures
|
||||
|
||||
use core::{fmt, mem::size_of};
|
||||
|
||||
use crate::{net::Ipv4Addr, util};
|
||||
use alloc::{string::String, vec, vec::Vec};
|
||||
|
||||
use super::IpAddr;
|
||||
|
||||
// TODO rewrite using NetValue
|
||||
|
||||
// "Wire" format
|
||||
|
||||
/// Raw DNS message header
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[repr(C, packed)]
|
||||
pub struct DnsHeader {
|
||||
/// Transaction ID
|
||||
pub xid: u16,
|
||||
/// Information about the message
|
||||
pub flags: u16,
|
||||
/// Question count
|
||||
pub qdcount: u16,
|
||||
/// Answer count
|
||||
pub ancount: u16,
|
||||
/// Nameserver record count
|
||||
pub nscount: u16,
|
||||
/// Additional record count
|
||||
pub arcount: u16,
|
||||
}
|
||||
|
||||
/// DNS query/reply type
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[non_exhaustive]
|
||||
#[repr(transparent)]
|
||||
pub struct DnsType(u16);
|
||||
|
||||
/// DNS query/reply class
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[non_exhaustive]
|
||||
#[repr(transparent)]
|
||||
pub struct DnsClass(u16);
|
||||
|
||||
/// Describes whether a message is a query or a reply
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[non_exhaustive]
|
||||
#[repr(transparent)]
|
||||
pub struct DnsMethod(u16);
|
||||
|
||||
/// Describes the reply code of the message
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[non_exhaustive]
|
||||
#[repr(transparent)]
|
||||
pub struct DnsReplyCode(u16);
|
||||
|
||||
impl DnsType {
|
||||
/// A - Address (when used with IN class: IPv4)
|
||||
pub const A: Self = Self(0x0001);
|
||||
/// OPT - Option data
|
||||
pub const OPT: Self = Self(0x0029);
|
||||
}
|
||||
|
||||
impl DnsClass {
|
||||
/// IN - Internet
|
||||
pub const IN: Self = Self(0x0001);
|
||||
}
|
||||
|
||||
impl DnsMethod {
|
||||
/// The message is a query
|
||||
pub const QUERY: Self = Self(0);
|
||||
/// The message is a reply
|
||||
pub const REPLY: Self = Self(1);
|
||||
}
|
||||
|
||||
impl DnsReplyCode {
|
||||
/// Successful status
|
||||
pub const NO_ERROR: Self = Self(0);
|
||||
/// Server could not recognize the message properly
|
||||
pub const FORMAT_ERROR: Self = Self(1);
|
||||
/// Server experienced an error
|
||||
pub const SERVER_ERROR: Self = Self(2);
|
||||
/// The name queried was invalid
|
||||
pub const NAME_ERROR: Self = Self(3);
|
||||
/// Feature is not implemented
|
||||
pub const NOT_IMPLEMENTED: Self = Self(4);
|
||||
/// The server refused to reply
|
||||
pub const REFUSED: Self = Self(5);
|
||||
}
|
||||
|
||||
impl fmt::Display for DnsReplyCode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let text = match *self {
|
||||
Self::NO_ERROR => "No error",
|
||||
Self::FORMAT_ERROR => "Format error",
|
||||
Self::SERVER_ERROR => "Server error",
|
||||
Self::NAME_ERROR => "Name error",
|
||||
Self::NOT_IMPLEMENTED => "Not implemented",
|
||||
Self::REFUSED => "Refused",
|
||||
_ => "<Unknown error>",
|
||||
};
|
||||
f.write_str(text)
|
||||
}
|
||||
}
|
||||
|
||||
// "Local" format
|
||||
|
||||
/// Describes a single name query
|
||||
#[derive(Debug)]
|
||||
pub struct DnsQuery {
|
||||
/// Name being queried
|
||||
pub name: DnsName,
|
||||
/// Type
|
||||
pub ty: DnsType,
|
||||
/// Class
|
||||
pub class: DnsClass,
|
||||
}
|
||||
|
||||
/// Describes an additional DNS option
|
||||
#[derive(Debug)]
|
||||
pub enum DnsOption {
|
||||
/// "Cookie" option to identify queries
|
||||
Cookie(u64),
|
||||
}
|
||||
|
||||
/// Represents a DNS name: a sequence of dot-separated domain levels if Some or NULL if None
|
||||
#[derive(Debug)]
|
||||
pub struct DnsName(pub Option<String>);
|
||||
|
||||
/// Describes a DNS additional/nameserver/answer record data
|
||||
#[derive(Debug)]
|
||||
pub enum DnsRecordData {
|
||||
/// A-record: address
|
||||
A(Ipv4Addr),
|
||||
/// Option-record: additional options
|
||||
Option(DnsOption),
|
||||
}
|
||||
|
||||
/// Describes a DNS record
|
||||
#[derive(Debug)]
|
||||
pub struct DnsRecord {
|
||||
/// DNS name this record is related to
|
||||
pub name: DnsName,
|
||||
/// Type of the record
|
||||
pub ty: DnsType,
|
||||
/// Class of the record
|
||||
pub class: DnsClass,
|
||||
/// TTL of the record (0 and ignored for Option records)
|
||||
pub ttl: u32,
|
||||
/// Data of the record
|
||||
pub rdata: DnsRecordData,
|
||||
}
|
||||
|
||||
/// Describes a single DNS query/reply
|
||||
#[derive(Debug)]
|
||||
pub struct DnsMessage {
|
||||
/// Transaction ID
|
||||
pub xid: u16,
|
||||
/// Query or reply
|
||||
pub method: DnsMethod,
|
||||
/// Reply code
|
||||
pub reply_code: DnsReplyCode,
|
||||
/// Question section
|
||||
pub questions: Vec<DnsQuery>,
|
||||
/// Answer section
|
||||
pub answers: Vec<DnsRecord>,
|
||||
/// Additional section
|
||||
pub additional: Vec<DnsRecord>,
|
||||
}
|
||||
|
||||
/// Helper trait for an abstract "buffer" which can be written to and extended
|
||||
pub trait ExtensibleBuffer {
|
||||
/// Writes a slice of bytes to the buffer
|
||||
fn write_slice(&mut self, slice: &[u8]);
|
||||
/// Writes a byte to the buffer
|
||||
fn write_byte(&mut self, byte: u8);
|
||||
|
||||
/// Writes value's bytes to the buffer
|
||||
#[cfg(feature = "bytemuck")]
|
||||
fn write_value<T: bytemuck::Pod>(&mut self, value: &T) {
|
||||
self.write_slice(bytemuck::bytes_of(value))
|
||||
}
|
||||
|
||||
/// Writes value's bytes to the buffer
|
||||
#[cfg(not(feature = "bytemuck"))]
|
||||
fn write_value<T>(&mut self, value: &T) {
|
||||
self.write_slice(util::bytes_of(value))
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper trait for DNS message parsing/serialization
|
||||
pub trait DnsSerialize {
|
||||
/// Serializes the message into the `output` buffer
|
||||
fn serialize<V: ExtensibleBuffer>(&self, output: &mut V);
|
||||
}
|
||||
|
||||
impl DnsName {
|
||||
/// "NULL" DNS name: an empty name
|
||||
pub const NULL: Self = Self(None);
|
||||
}
|
||||
|
||||
impl DnsMessage {
|
||||
/// Constructs a DNS query message
|
||||
pub fn query<S: Into<String>>(name: S, ty: DnsType, xid: u16, cookie: u64) -> Self {
|
||||
Self {
|
||||
xid,
|
||||
method: DnsMethod::QUERY,
|
||||
reply_code: DnsReplyCode::NO_ERROR,
|
||||
questions: vec![DnsQuery {
|
||||
name: DnsName(Some(name.into())),
|
||||
ty,
|
||||
class: DnsClass::IN,
|
||||
}],
|
||||
answers: vec![],
|
||||
additional: vec![DnsRecord {
|
||||
name: DnsName::NULL,
|
||||
ty: DnsType::OPT,
|
||||
class: DnsClass(1232),
|
||||
ttl: 0,
|
||||
rdata: DnsRecordData::Option(DnsOption::Cookie(cookie)),
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a DNS reply message
|
||||
pub fn reply(
|
||||
reply_code: DnsReplyCode,
|
||||
questions: Vec<DnsQuery>,
|
||||
answers: Vec<(String, IpAddr)>,
|
||||
xid: u16,
|
||||
) -> Self {
|
||||
Self {
|
||||
xid,
|
||||
reply_code,
|
||||
method: DnsMethod::REPLY,
|
||||
questions,
|
||||
answers: answers
|
||||
.into_iter()
|
||||
.map(|(name, addr)| {
|
||||
let name = DnsName(Some(name));
|
||||
let (ty, rdata) = match addr {
|
||||
IpAddr::V4(addr) => (DnsType::A, DnsRecordData::A(addr)),
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
DnsRecord {
|
||||
name,
|
||||
ty,
|
||||
class: DnsClass::IN,
|
||||
ttl: 100,
|
||||
rdata,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
additional: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtensibleBuffer for Vec<u8> {
|
||||
fn write_slice(&mut self, slice: &[u8]) {
|
||||
self.extend_from_slice(slice);
|
||||
}
|
||||
|
||||
fn write_byte(&mut self, byte: u8) {
|
||||
self.push(byte);
|
||||
}
|
||||
}
|
||||
|
||||
mod ser {
|
||||
use super::*;
|
||||
|
||||
impl DnsSerialize for DnsName {
|
||||
fn serialize<V: ExtensibleBuffer>(&self, output: &mut V) {
|
||||
if let Some(name) = &self.0 {
|
||||
// Write question
|
||||
for word in name.split('.') {
|
||||
if word.is_empty() {
|
||||
continue;
|
||||
}
|
||||
output.write_byte(word.len().try_into().unwrap());
|
||||
output.write_slice(word.as_bytes());
|
||||
}
|
||||
}
|
||||
// Zero-terminate
|
||||
output.write_byte(0);
|
||||
}
|
||||
}
|
||||
|
||||
impl DnsSerialize for DnsOption {
|
||||
fn serialize<V: ExtensibleBuffer>(&self, output: &mut V) {
|
||||
match self {
|
||||
&Self::Cookie(value) => {
|
||||
output.write_slice(&12u16.to_be_bytes()); // RDLENGTH
|
||||
output.write_slice(&10u16.to_be_bytes()); // Option = COOKIE
|
||||
output.write_slice(&8u16.to_be_bytes()); // Cookie length
|
||||
output.write_slice(&value.to_be_bytes()); // Cookie data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DnsSerialize for DnsRecordData {
|
||||
fn serialize<V: ExtensibleBuffer>(&self, output: &mut V) {
|
||||
match self {
|
||||
&Self::A(addr) => {
|
||||
output.write_slice(&4u16.to_be_bytes());
|
||||
output.write_slice(&u32::from(addr).to_be_bytes());
|
||||
}
|
||||
Self::Option(option) => option.serialize(output),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DnsSerialize for DnsRecord {
|
||||
fn serialize<V: ExtensibleBuffer>(&self, output: &mut V) {
|
||||
self.name.serialize(output);
|
||||
output.write_slice(&self.ty.0.to_be_bytes());
|
||||
output.write_slice(&self.class.0.to_be_bytes());
|
||||
output.write_slice(&self.ttl.to_be_bytes());
|
||||
self.rdata.serialize(output);
|
||||
}
|
||||
}
|
||||
|
||||
impl DnsSerialize for DnsQuery {
|
||||
fn serialize<V: ExtensibleBuffer>(&self, output: &mut V) {
|
||||
self.name.serialize(output);
|
||||
output.write_slice(&self.ty.0.to_be_bytes());
|
||||
output.write_slice(&self.class.0.to_be_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
impl DnsSerialize for DnsMessage {
|
||||
fn serialize<V: ExtensibleBuffer>(&self, output: &mut V) {
|
||||
let qdcount: u16 = self.questions.len().try_into().unwrap();
|
||||
let ancount: u16 = self.answers.len().try_into().unwrap();
|
||||
let arcount: u16 = self.additional.len().try_into().unwrap();
|
||||
|
||||
let header = DnsHeader {
|
||||
xid: self.xid.to_be(),
|
||||
flags: ((self.method.0 << 15) | (self.reply_code.0 & 0xF) | (1 << 8)).to_be(),
|
||||
qdcount: qdcount.to_be(),
|
||||
nscount: 0,
|
||||
ancount: ancount.to_be(),
|
||||
arcount: arcount.to_be(),
|
||||
};
|
||||
|
||||
output.write_slice(util::bytes_of(&header));
|
||||
|
||||
// Serialize data sections
|
||||
for question in self.questions.iter() {
|
||||
question.serialize(output);
|
||||
}
|
||||
for answer in self.answers.iter() {
|
||||
answer.serialize(output);
|
||||
}
|
||||
for additional in self.additional.iter() {
|
||||
additional.serialize(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
mod de {
|
||||
use super::*;
|
||||
|
||||
impl DnsName {
|
||||
pub fn parse(message: &[u8], input: &[u8]) -> Option<(Self, usize)> {
|
||||
let mut pos = 0;
|
||||
let mut value = String::new();
|
||||
|
||||
loop {
|
||||
if input.len() < pos + 1 {
|
||||
return None;
|
||||
}
|
||||
let len = input[pos];
|
||||
pos += 1;
|
||||
|
||||
if len == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
match len & 0b11000000 {
|
||||
0 => {
|
||||
let len = len as usize;
|
||||
|
||||
if input.len() < pos + len {
|
||||
return None;
|
||||
}
|
||||
let word = core::str::from_utf8(&input[pos..pos + len]).ok()?;
|
||||
value.push_str(word);
|
||||
value.push('.');
|
||||
pos += len;
|
||||
}
|
||||
0b11000000 if len == 0b11000000 => {
|
||||
// TODO pointer
|
||||
if input.len() < pos + 1 {
|
||||
return None;
|
||||
}
|
||||
let off = input[pos] as usize;
|
||||
if input.len() < off {
|
||||
return None;
|
||||
}
|
||||
let (rest, _) = Self::parse(message, &message[off..])?;
|
||||
return Some((rest, pos + 1));
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
|
||||
if value.is_empty() {
|
||||
Some((Self::NULL, pos))
|
||||
} else {
|
||||
Some((Self(Some(value)), pos))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DnsRecordData {
|
||||
pub fn parse(
|
||||
ty: DnsType,
|
||||
class: DnsClass,
|
||||
_message: &[u8],
|
||||
input: &[u8],
|
||||
) -> Option<(Option<Self>, usize)> {
|
||||
if input.len() < size_of::<u16>() {
|
||||
return None;
|
||||
}
|
||||
let rdlength = u16::from_be_bytes([input[0], input[1]]) as usize;
|
||||
if input.len() < size_of::<u16>() + rdlength {
|
||||
return None;
|
||||
}
|
||||
|
||||
let value = match (ty, class) {
|
||||
(DnsType::A, DnsClass::IN) if rdlength == 4 => {
|
||||
let value = u32::from_be_bytes([input[2], input[3], input[4], input[5]]);
|
||||
Some(Self::A(value.into()))
|
||||
}
|
||||
(DnsType::OPT, _) => {
|
||||
todo!();
|
||||
}
|
||||
(_, _) => None,
|
||||
};
|
||||
|
||||
Some((value, rdlength + size_of::<u16>()))
|
||||
}
|
||||
}
|
||||
|
||||
impl DnsRecord {
|
||||
pub fn parse(message: &[u8], input: &[u8]) -> Option<(Option<Self>, usize)> {
|
||||
if input.len() < 1 + 4 * size_of::<u16>() {
|
||||
return None;
|
||||
}
|
||||
let (name, len) = DnsName::parse(message, input)?;
|
||||
if input.len() < len + 4 * size_of::<u16>() {
|
||||
return None;
|
||||
}
|
||||
let ty = DnsType(u16::from_be_bytes([input[len], input[len + 1]]));
|
||||
let class = DnsClass(u16::from_be_bytes([input[len + 2], input[len + 3]]));
|
||||
let ttl = u32::from_be_bytes([
|
||||
input[len + 4],
|
||||
input[len + 5],
|
||||
input[len + 6],
|
||||
input[len + 7],
|
||||
]);
|
||||
let (rdata, rdata_len) = DnsRecordData::parse(ty, class, message, &input[len + 8..])?;
|
||||
|
||||
let value = rdata.map(|v| Self {
|
||||
name,
|
||||
ty,
|
||||
class,
|
||||
ttl,
|
||||
rdata: v,
|
||||
});
|
||||
|
||||
Some((value, rdata_len + len + 4 * size_of::<u16>()))
|
||||
}
|
||||
}
|
||||
|
||||
impl DnsQuery {
|
||||
pub fn parse(message: &[u8], input: &[u8]) -> Option<(Self, usize)> {
|
||||
if input.len() < 1 + 2 * size_of::<u16>() {
|
||||
return None;
|
||||
}
|
||||
let (name, len) = DnsName::parse(message, input)?;
|
||||
if input.len() < len + 2 * size_of::<u16>() {
|
||||
return None;
|
||||
}
|
||||
let ty = DnsType(u16::from_be_bytes([input[len], input[len + 1]]));
|
||||
let class = DnsClass(u16::from_be_bytes([input[len + 2], input[len + 3]]));
|
||||
|
||||
Some((Self { name, ty, class }, len + 2 * size_of::<u16>()))
|
||||
}
|
||||
}
|
||||
|
||||
impl DnsMessage {
|
||||
pub fn parse(input: &[u8]) -> Option<Self> {
|
||||
if input.len() < size_of::<DnsHeader>() {
|
||||
return None;
|
||||
}
|
||||
let header: &DnsHeader = util::from_bytes(&input[..size_of::<DnsHeader>()]);
|
||||
|
||||
let method = DnsMethod((u16::from_be(header.flags) >> 15) & 1);
|
||||
let qdcount = u16::from_be(header.qdcount);
|
||||
let ancount = u16::from_be(header.ancount);
|
||||
|
||||
let reply_code = if method == DnsMethod::REPLY {
|
||||
let value = u16::from_be(header.flags) & 0xF;
|
||||
if value > 5 {
|
||||
return None;
|
||||
}
|
||||
DnsReplyCode(value)
|
||||
} else {
|
||||
DnsReplyCode::NO_ERROR
|
||||
};
|
||||
|
||||
// TODO don't ignore arcount/nscount
|
||||
|
||||
let mut questions = vec![];
|
||||
let mut answers = vec![];
|
||||
|
||||
let mut offset = size_of::<DnsHeader>();
|
||||
for _ in 0..qdcount {
|
||||
let (question, len) = DnsQuery::parse(input, &input[offset..])?;
|
||||
questions.push(question);
|
||||
offset += len;
|
||||
}
|
||||
for _ in 0..ancount {
|
||||
let (answer, len) = DnsRecord::parse(input, &input[offset..])?;
|
||||
if let Some(answer) = answer {
|
||||
answers.push(answer);
|
||||
}
|
||||
offset += len;
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
xid: u16::from_be(header.xid),
|
||||
method,
|
||||
reply_code,
|
||||
questions,
|
||||
answers,
|
||||
additional: vec![],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
60
lib/abi/src/net/mod.rs
Normal file
60
lib/abi/src/net/mod.rs
Normal file
@ -0,0 +1,60 @@
|
||||
//! Defines data types for network operations
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "rustc_std_alloc"))]
|
||||
pub mod dns;
|
||||
#[cfg(feature = "alloc")]
|
||||
pub mod netconfig;
|
||||
|
||||
pub mod protocols;
|
||||
pub mod types;
|
||||
|
||||
pub use crate::generated::SocketType;
|
||||
|
||||
pub use types::{
|
||||
ip_addr::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||
socket_addr::{SocketAddr, SocketAddrV4, SocketAddrV6},
|
||||
subnet_addr::{SubnetAddr, SubnetV4Addr},
|
||||
MacAddress,
|
||||
};
|
||||
|
||||
// primitive_enum! {
|
||||
// #[doc = "Defines a type of a socket"]
|
||||
// pub enum SocketType: u32 {
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Describes a method to query an interface
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SocketInterfaceQuery<'a> {
|
||||
/// Query by name
|
||||
ByName(&'a str),
|
||||
/// Query by id
|
||||
ById(u32),
|
||||
}
|
||||
|
||||
/// Describes a socket operation parameter
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SocketOption<'a> {
|
||||
/// Whether the socket is broadcast
|
||||
Broadcast(bool),
|
||||
/// Time-to-Live parameter
|
||||
Ttl(u32),
|
||||
/// Bind the socket to a specific interface
|
||||
BindInterface(SocketInterfaceQuery<'a>),
|
||||
/// Unbind the socket from an interface
|
||||
UnbindInterface,
|
||||
/// (Read-only) Hardware address of the bound interface
|
||||
BoundHardwareAddress(MacAddress),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for SocketInterfaceQuery<'a> {
|
||||
fn from(value: &'a str) -> Self {
|
||||
Self::ByName(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for SocketInterfaceQuery<'_> {
|
||||
fn from(value: u32) -> Self {
|
||||
Self::ById(value)
|
||||
}
|
||||
}
|
126
lib/abi/src/net/netconfig.rs
Normal file
126
lib/abi/src/net/netconfig.rs
Normal file
@ -0,0 +1,126 @@
|
||||
//! Network configuration service messages
|
||||
|
||||
use crate::alloc::boxed::Box;
|
||||
|
||||
use super::{IpAddr, MacAddress, SubnetAddr};
|
||||
|
||||
/// Describes a single route
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct RouteInfo {
|
||||
/// Interface textual name (unique)
|
||||
pub interface_name: Box<str>,
|
||||
/// Interface unique ID number
|
||||
pub interface_id: u32,
|
||||
/// Subnetwork this route is related to
|
||||
pub subnet: SubnetAddr,
|
||||
/// Gateway this route should be reached through
|
||||
pub gateway: Option<IpAddr>,
|
||||
}
|
||||
|
||||
/// Describes routing information about some address
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct RoutingInfo {
|
||||
/// Interface textual name (unique)
|
||||
pub interface_name: Box<str>,
|
||||
/// Interface unique ID number
|
||||
pub interface_id: u32,
|
||||
/// Gateway this route should be reached through
|
||||
pub gateway: Option<IpAddr>,
|
||||
/// Real destination which should be used
|
||||
pub destination: IpAddr,
|
||||
/// IP address used as a source when sending through this route
|
||||
pub source: Option<IpAddr>,
|
||||
/// Interface MAC address
|
||||
pub source_mac: MacAddress,
|
||||
}
|
||||
|
||||
/// Describes a single interface
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct InterfaceInfo {
|
||||
/// Interface textual name (unique)
|
||||
pub interface_name: Box<str>,
|
||||
/// Interface unique ID number
|
||||
pub interface_id: u32,
|
||||
/// Interface IP address
|
||||
pub address: Option<IpAddr>,
|
||||
/// Interface hardware address
|
||||
pub mac: MacAddress,
|
||||
}
|
||||
|
||||
/// Describes a query to some interface either by its name or ID
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum InterfaceQuery {
|
||||
/// Query by ID number
|
||||
ById(u32),
|
||||
/// Query by name
|
||||
ByName(Box<str>),
|
||||
}
|
||||
|
||||
/// kernel-netconf interface request
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum NetConfigRequest {
|
||||
/// List all routes
|
||||
ListRoutes,
|
||||
/// List all interfaces
|
||||
ListInterfaces,
|
||||
/// Describe routes related to a single interface
|
||||
DescribeRoutes(InterfaceQuery),
|
||||
/// Describe a single interface
|
||||
DescribeInterface(InterfaceQuery),
|
||||
/// Add a route
|
||||
AddRoute {
|
||||
/// Interface to which the action applies
|
||||
interface: InterfaceQuery,
|
||||
/// Optional gateway to reach the route through
|
||||
gateway: Option<IpAddr>,
|
||||
/// Subnetwork of the route
|
||||
subnet: SubnetAddr,
|
||||
},
|
||||
/// Set interface's IP address
|
||||
SetNetworkAddress {
|
||||
/// Interface to which the action applies
|
||||
interface: InterfaceQuery,
|
||||
/// Address to set
|
||||
address: IpAddr,
|
||||
},
|
||||
/// Clear interface's IP address
|
||||
ClearNetworkAddress(InterfaceQuery),
|
||||
/// Query a route and return routing information
|
||||
QueryRoute(IpAddr),
|
||||
/// Query the MAC address of the specified IP
|
||||
QueryArp(u32, IpAddr, bool),
|
||||
}
|
||||
|
||||
/// Wrapper to provide error status in replies
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum NetConfigResult<T> {
|
||||
/// Success
|
||||
Ok(T),
|
||||
/// Error
|
||||
Err(Box<str>),
|
||||
}
|
||||
|
||||
impl<T> NetConfigResult<T> {
|
||||
/// Constructs an error value
|
||||
pub fn err<S: Into<Box<str>>>(message: S) -> Self {
|
||||
Self::Err(message.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for InterfaceQuery {
|
||||
fn from(value: &str) -> Self {
|
||||
Self::ByName(Box::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for InterfaceQuery {
|
||||
fn from(value: u32) -> Self {
|
||||
Self::ById(value)
|
||||
}
|
||||
}
|
236
lib/abi/src/net/protocols.rs
Normal file
236
lib/abi/src/net/protocols.rs
Normal file
@ -0,0 +1,236 @@
|
||||
// TODO don't really want to document this now
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use core::{fmt, mem::size_of};
|
||||
|
||||
use crate::bitflags;
|
||||
|
||||
use super::{
|
||||
types::{net_value::WrappedValue, NetValue, NetValueImpl},
|
||||
MacAddress,
|
||||
};
|
||||
|
||||
/////////// Layer 2 protocols ////////////
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[repr(C)]
|
||||
pub struct EthernetFrame {
|
||||
pub destination_mac: MacAddress,
|
||||
pub source_mac: MacAddress,
|
||||
pub ethertype: NetValue<EtherType>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[repr(transparent)]
|
||||
pub struct EtherType(u16);
|
||||
|
||||
impl EtherType {
|
||||
pub const ARP: Self = Self(0x0806);
|
||||
pub const IPV4: Self = Self(0x0800);
|
||||
}
|
||||
|
||||
impl WrappedValue for EtherType {
|
||||
type Inner = u16;
|
||||
|
||||
fn into_inner(self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
fn from_inner(value: u16) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
/////////// Layer 3 protocols ////////////
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[repr(C, packed)]
|
||||
pub struct ArpFrame {
|
||||
pub hardware_type: NetValue<u16>,
|
||||
pub protocol: NetValue<EtherType>,
|
||||
pub hardware_size: u8,
|
||||
pub protocol_size: u8,
|
||||
pub opcode: NetValue<u16>,
|
||||
pub sender_mac: MacAddress,
|
||||
pub sender_ip: NetValue<u32>,
|
||||
pub target_mac: MacAddress,
|
||||
// TODO handle IPv6
|
||||
pub target_ip: NetValue<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[repr(C, packed)]
|
||||
pub struct Ipv4Frame {
|
||||
pub version_length: u8,
|
||||
pub dscp_flags: u8,
|
||||
pub total_length: NetValue<u16>,
|
||||
pub id: NetValue<u16>,
|
||||
pub flags_frag: NetValue<u16>,
|
||||
pub ttl: u8,
|
||||
pub protocol: IpProtocol,
|
||||
pub header_checksum: NetValue<u16>,
|
||||
pub source_address: NetValue<u32>,
|
||||
pub destination_address: NetValue<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[repr(transparent)]
|
||||
pub struct IpProtocol(pub u8);
|
||||
|
||||
impl IpProtocol {
|
||||
pub const ICMP: Self = Self(1);
|
||||
pub const TCP: Self = Self(6);
|
||||
pub const UDP: Self = Self(17);
|
||||
}
|
||||
|
||||
/////////// Layer 4 protocols ////////////
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[repr(C, packed)]
|
||||
pub struct UdpFrame {
|
||||
pub source_port: NetValue<u16>,
|
||||
pub destination_port: NetValue<u16>,
|
||||
pub length: NetValue<u16>,
|
||||
pub checksum: NetValue<u16>,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct TcpFlags: u8 {
|
||||
const FIN: bit 0;
|
||||
const SYN: bit 1;
|
||||
const RST: bit 2;
|
||||
const PSH: bit 3;
|
||||
const ACK: bit 4;
|
||||
const URG: bit 5;
|
||||
const ECE: bit 6;
|
||||
const CWR: bit 7;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[repr(C, packed)]
|
||||
pub struct TcpFrame {
|
||||
pub source_port: NetValue<u16>,
|
||||
pub destination_port: NetValue<u16>,
|
||||
pub sequence_number: NetValue<u32>,
|
||||
pub acknowledge_number: NetValue<u32>,
|
||||
pub data_offset: u8,
|
||||
pub flags: TcpFlags,
|
||||
pub window_size: NetValue<u16>,
|
||||
pub checksum: NetValue<u16>,
|
||||
pub urgent_pointer: NetValue<u16>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[repr(C, packed)]
|
||||
pub struct TcpV4PseudoHeader {
|
||||
pub source_address: NetValue<u32>,
|
||||
pub destination_address: NetValue<u32>,
|
||||
pub _zero: u8,
|
||||
pub protocol: IpProtocol,
|
||||
pub tcp_length: NetValue<u16>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[repr(C, packed)]
|
||||
pub struct IcmpV4Frame {
|
||||
pub ty: u8,
|
||||
pub code: u8,
|
||||
pub checksum: NetValue<u16>,
|
||||
pub rest: NetValue<u32>,
|
||||
}
|
||||
|
||||
/////////// Helper utilities /////////////
|
||||
|
||||
pub struct InetChecksum {
|
||||
state: u32,
|
||||
}
|
||||
|
||||
///////////// Implementations ////////////
|
||||
|
||||
/////////// Layer 3 protocols ////////////
|
||||
|
||||
impl fmt::Display for IpProtocol {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
Self::ICMP => f.write_str("ICMP"),
|
||||
Self::UDP => f.write_str("UDP"),
|
||||
Self::TCP => f.write_str("TCP"),
|
||||
_ => f.write_str("<unknown-l4-proto>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ipv4Frame {
|
||||
pub fn header_length(&self) -> usize {
|
||||
core::cmp::max(
|
||||
(self.version_length & 0xF) << 2,
|
||||
size_of::<Ipv4Frame>() as u8,
|
||||
) as usize
|
||||
}
|
||||
|
||||
pub fn total_length(&self) -> usize {
|
||||
u16::from_network_order(self.total_length) as usize
|
||||
}
|
||||
}
|
||||
|
||||
/////////// Layer 4 protocols ////////////
|
||||
|
||||
impl UdpFrame {
|
||||
pub fn data_length(&self) -> usize {
|
||||
(u16::from_network_order(self.length) as usize).saturating_sub(size_of::<UdpFrame>())
|
||||
}
|
||||
}
|
||||
|
||||
impl TcpFrame {
|
||||
pub fn data_offset(&self) -> usize {
|
||||
((self.data_offset >> 4) as usize * size_of::<u32>()).max(size_of::<TcpFrame>())
|
||||
}
|
||||
}
|
||||
|
||||
/////////// Helper utilities /////////////
|
||||
|
||||
impl InetChecksum {
|
||||
pub fn new() -> Self {
|
||||
Self { state: 0 }
|
||||
}
|
||||
|
||||
#[cfg(feature = "bytemuck")]
|
||||
pub fn add_value<T: bytemuck::Pod>(&mut self, value: &T, reorder: bool) {
|
||||
self.add_bytes(bytemuck::bytes_of(value), reorder)
|
||||
}
|
||||
|
||||
pub fn add_bytes(&mut self, bytes: &[u8], reorder: bool) {
|
||||
let len = bytes.len();
|
||||
for i in 0..len / 2 {
|
||||
let word = if reorder {
|
||||
((bytes[i * 2] as u16) << 8) | (bytes[i * 2 + 1] as u16)
|
||||
} else {
|
||||
(bytes[i * 2] as u16) | ((bytes[i * 2 + 1] as u16) << 8)
|
||||
};
|
||||
self.state = self.state.wrapping_add(word as u32);
|
||||
}
|
||||
if len % 2 != 0 {
|
||||
let word = if reorder {
|
||||
(bytes[len - 1] as u16) << 8
|
||||
} else {
|
||||
todo!()
|
||||
};
|
||||
self.state = self.state.wrapping_add(word as u32);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finish(self) -> u16 {
|
||||
let sum = (self.state >> 16) + (self.state & 0xFFFF);
|
||||
|
||||
(!(sum & 0xFFFF)) as u16
|
||||
}
|
||||
}
|
200
lib/abi/src/net/types/ip_addr.rs
Normal file
200
lib/abi/src/net/types/ip_addr.rs
Normal file
@ -0,0 +1,200 @@
|
||||
//! IP-address related types, see [core::net::IpAddr]
|
||||
use core::{fmt, str::FromStr};
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
/// Describes an IPv4 address
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[repr(C)]
|
||||
pub struct Ipv4Addr([u8; 4]);
|
||||
|
||||
/// Describes an IPv6 address
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[repr(C)]
|
||||
pub struct Ipv6Addr([u8; 16]);
|
||||
|
||||
/// Describes an IPv4/IPv6 address
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[repr(C)]
|
||||
pub enum IpAddr {
|
||||
/// IPv4 address
|
||||
V4(Ipv4Addr),
|
||||
/// IPv6 address
|
||||
V6(Ipv6Addr),
|
||||
}
|
||||
|
||||
// IPv4
|
||||
|
||||
impl Ipv4Addr {
|
||||
/// An unspecified IPv4 address: 0.0.0.0
|
||||
pub const UNSPECIFIED: Self = Self([0, 0, 0, 0]);
|
||||
/// A broadcast IPv4 address: 255.255.255.255
|
||||
pub const BROADCAST: Self = Self([255, 255, 255, 255]);
|
||||
/// A loopback IPv4 address: 127.0.0.1
|
||||
pub const LOOPBACK: Self = Self([127, 0, 0, 1]);
|
||||
|
||||
/// Constructs a new IPv4 address from its octets
|
||||
pub fn new(x: u8, y: u8, z: u8, w: u8) -> Self {
|
||||
Self([x, y, z, w])
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Ipv4Addr {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let c = core::net::Ipv4Addr::from_str(s).map_err(|_| Error::InvalidArgument)?;
|
||||
Ok(Self(c.octets()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Ipv4Addr {
|
||||
fn from(value: u32) -> Self {
|
||||
Self(value.to_be_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ipv4Addr> for u32 {
|
||||
fn from(value: Ipv4Addr) -> Self {
|
||||
u32::from_be_bytes(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Ipv4Addr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&core::net::Ipv4Addr::from(self.0), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<core::net::Ipv4Addr> for Ipv4Addr {
|
||||
fn from(value: core::net::Ipv4Addr) -> Self {
|
||||
Self(value.octets())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ipv4Addr> for core::net::Ipv4Addr {
|
||||
fn from(value: Ipv4Addr) -> Self {
|
||||
core::net::Ipv4Addr::from(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
// IPv6
|
||||
|
||||
impl FromStr for Ipv6Addr {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let c = core::net::Ipv6Addr::from_str(s).map_err(|_| Error::InvalidArgument)?;
|
||||
Ok(Self(c.octets()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 16]> for Ipv6Addr {
|
||||
#[inline]
|
||||
fn from(value: [u8; 16]) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ipv6Addr> for [u8; 16] {
|
||||
#[inline]
|
||||
fn from(value: Ipv6Addr) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Ipv6Addr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&core::net::Ipv6Addr::from(self.0), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<core::net::Ipv6Addr> for Ipv6Addr {
|
||||
#[inline]
|
||||
fn from(value: core::net::Ipv6Addr) -> Self {
|
||||
Self(value.octets())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ipv6Addr> for core::net::Ipv6Addr {
|
||||
#[inline]
|
||||
fn from(value: Ipv6Addr) -> Self {
|
||||
core::net::Ipv6Addr::from(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
// IP
|
||||
|
||||
impl IpAddr {
|
||||
/// Returns `true` if the address is IPv4
|
||||
pub fn is_ipv4(&self) -> bool {
|
||||
matches!(self, Self::V4(_))
|
||||
}
|
||||
/// Returns `true` if the address is IPv6
|
||||
pub fn is_ipv6(&self) -> bool {
|
||||
matches!(self, Self::V6(_))
|
||||
}
|
||||
|
||||
/// Returns [Some] if this is an IPv4 address, [None] otherwise
|
||||
pub fn into_ipv4(self) -> Option<Ipv4Addr> {
|
||||
match self {
|
||||
Self::V4(address) => Some(address),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for IpAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::V4(addr) => fmt::Display::fmt(addr, f),
|
||||
Self::V6(addr) => fmt::Display::fmt(addr, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for IpAddr {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let c = core::net::IpAddr::from_str(s).map_err(|_| Error::InvalidArgument)?;
|
||||
Ok(Self::from(c))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IpAddr> for core::net::IpAddr {
|
||||
#[inline]
|
||||
fn from(value: IpAddr) -> Self {
|
||||
match value {
|
||||
IpAddr::V4(address) => core::net::IpAddr::V4(address.into()),
|
||||
IpAddr::V6(address) => core::net::IpAddr::V6(address.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<core::net::IpAddr> for IpAddr {
|
||||
#[inline]
|
||||
fn from(value: core::net::IpAddr) -> Self {
|
||||
match value {
|
||||
core::net::IpAddr::V4(address) => Self::V4(address.into()),
|
||||
core::net::IpAddr::V6(address) => Self::V6(address.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ipv4Addr> for IpAddr {
|
||||
fn from(value: Ipv4Addr) -> Self {
|
||||
Self::V4(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ipv6Addr> for IpAddr {
|
||||
fn from(value: Ipv6Addr) -> Self {
|
||||
Self::V6(value)
|
||||
}
|
||||
}
|
49
lib/abi/src/net/types/mod.rs
Normal file
49
lib/abi/src/net/types/mod.rs
Normal file
@ -0,0 +1,49 @@
|
||||
//! Various network types
|
||||
|
||||
use core::fmt;
|
||||
|
||||
pub mod ip_addr;
|
||||
pub mod net_value;
|
||||
pub mod socket_addr;
|
||||
pub mod subnet_addr;
|
||||
|
||||
pub use net_value::{NetValue, NetValueImpl};
|
||||
|
||||
/// Describes a hardware MAC address
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[repr(transparent)]
|
||||
pub struct MacAddress([u8; 6]);
|
||||
|
||||
impl MacAddress {
|
||||
/// A broadcast MAC address
|
||||
pub const BROADCAST: Self = Self([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);
|
||||
/// An unspecified MAC address
|
||||
pub const UNSPECIFIED: Self = Self([0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
|
||||
}
|
||||
|
||||
impl From<[u8; 6]> for MacAddress {
|
||||
fn from(value: [u8; 6]) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MacAddress> for [u8; 6] {
|
||||
fn from(value: MacAddress) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MacAddress {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for (i, octet) in self.0.into_iter().enumerate() {
|
||||
if i != 0 {
|
||||
write!(f, ":")?;
|
||||
}
|
||||
write!(f, "{:02X}", octet)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
110
lib/abi/src/net/types/net_value.rs
Normal file
110
lib/abi/src/net/types/net_value.rs
Normal file
@ -0,0 +1,110 @@
|
||||
//! Network value handling utilities
|
||||
use core::fmt;
|
||||
|
||||
macro_rules! wrap_primitives {
|
||||
($($ty:ty),+) => {
|
||||
$(impl NetValueImpl for $ty {
|
||||
#[inline]
|
||||
fn from_network_order(value: NetValue<Self>) -> Self {
|
||||
Self::from_be(value.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn to_network_order(self) -> NetValue<Self> {
|
||||
NetValue(self.to_be())
|
||||
}
|
||||
})+
|
||||
};
|
||||
}
|
||||
|
||||
/// Interface for converting from/to network-format values
|
||||
#[cfg(not(feature = "bytemuck"))]
|
||||
pub trait NetValueImpl: Copy + Eq {
|
||||
/// Performs conversion from network to host format
|
||||
fn from_network_order(value: NetValue<Self>) -> Self;
|
||||
/// Performs conversion from host format to network
|
||||
fn to_network_order(self) -> NetValue<Self>;
|
||||
}
|
||||
|
||||
/// Interface for converting from/to network-format values
|
||||
#[cfg(feature = "bytemuck")]
|
||||
pub trait NetValueImpl: Copy + Eq + bytemuck::Pod + bytemuck::Zeroable {
|
||||
/// Performs conversion from network to host format
|
||||
fn from_network_order(value: NetValue<Self>) -> Self;
|
||||
/// Performs conversion from host format to network
|
||||
fn to_network_order(self) -> NetValue<Self>;
|
||||
}
|
||||
|
||||
/// Wrapper type to ensure network values are handled properly
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[repr(transparent)]
|
||||
pub struct NetValue<T: NetValueImpl>(T);
|
||||
|
||||
/// Helper type for primitive values wrapped in some 1-struct
|
||||
#[cfg(not(feature = "bytemuck"))]
|
||||
pub trait WrappedValue: Copy + Eq {
|
||||
/// Wrapped type
|
||||
type Inner: NetValueImpl;
|
||||
|
||||
/// Converts from self to wrapped type
|
||||
fn into_inner(self) -> Self::Inner;
|
||||
/// Converts from wrapped type to self
|
||||
fn from_inner(value: Self::Inner) -> Self;
|
||||
}
|
||||
|
||||
/// Helper type for primitive values wrapped in some 1-struct
|
||||
#[cfg(feature = "bytemuck")]
|
||||
pub trait WrappedValue: Copy + Eq + bytemuck::Pod + bytemuck::Zeroable {
|
||||
/// Wrapped type
|
||||
type Inner: NetValueImpl;
|
||||
|
||||
/// Converts from self to wrapped type
|
||||
fn into_inner(self) -> Self::Inner;
|
||||
/// Converts from wrapped type to self
|
||||
fn from_inner(value: Self::Inner) -> Self;
|
||||
}
|
||||
|
||||
impl<T: NetValueImpl> NetValue<T> {
|
||||
/// Creates a [NetValue] from a value that's already in network order
|
||||
#[inline]
|
||||
pub fn from_network_order(value: T) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: WrappedValue> NetValue<T> {
|
||||
/// "Extracts" a wrapped value from [NetValue]
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> NetValue<T::Inner> {
|
||||
NetValue(self.0.into_inner())
|
||||
}
|
||||
|
||||
/// Converts a wrapped value into its "shell" [NetValue] type
|
||||
#[inline]
|
||||
pub fn from_inner(value: NetValue<T::Inner>) -> NetValue<T> {
|
||||
NetValue(T::from_inner(value.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NetValueImpl + fmt::Debug> fmt::Debug for NetValue<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let value = T::from_network_order(*self);
|
||||
fmt::Debug::fmt(&value, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: WrappedValue> NetValueImpl for T {
|
||||
#[inline]
|
||||
fn from_network_order(value: NetValue<T>) -> T {
|
||||
let inner = <T::Inner>::from_network_order(value.into_inner());
|
||||
T::from_inner(inner)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn to_network_order(self) -> NetValue<T> {
|
||||
NetValue::from_inner(self.into_inner().to_network_order())
|
||||
}
|
||||
}
|
||||
|
||||
wrap_primitives!(u16, u32, u64);
|
177
lib/abi/src/net/types/socket_addr.rs
Normal file
177
lib/abi/src/net/types/socket_addr.rs
Normal file
@ -0,0 +1,177 @@
|
||||
//! Socket address types
|
||||
use core::fmt;
|
||||
|
||||
use crate::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
/// Describes an IPv4 socket address: 1.2.3.4:443
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[repr(C)]
|
||||
pub struct SocketAddrV4(Ipv4Addr, u16);
|
||||
|
||||
/// Describes an IPv6 socket address: [::]:443
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[repr(C)]
|
||||
pub struct SocketAddrV6(Ipv6Addr, u16);
|
||||
|
||||
/// Describes a socket address
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum SocketAddr {
|
||||
/// IPv4 socket address
|
||||
V4(SocketAddrV4),
|
||||
/// IPv6 socket address
|
||||
V6(SocketAddrV6),
|
||||
}
|
||||
|
||||
// V4
|
||||
|
||||
impl SocketAddrV4 {
|
||||
/// "Null" IPv4 address used when sender/recepient is not known or cannot be represented
|
||||
pub const NULL: Self = Self::new(Ipv4Addr::UNSPECIFIED, 0);
|
||||
|
||||
/// Constructs an IPv4 socket address from its IP and port components
|
||||
pub const fn new(address: Ipv4Addr, port: u16) -> Self {
|
||||
Self(address, port)
|
||||
}
|
||||
|
||||
/// Returns the IP address part of the value
|
||||
pub const fn ip(&self) -> Ipv4Addr {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns the port part of the value
|
||||
pub const fn port(&self) -> u16 {
|
||||
self.1
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SocketAddrV4 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}:{}", self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<core::net::SocketAddrV4> for SocketAddrV4 {
|
||||
#[inline]
|
||||
fn from(value: core::net::SocketAddrV4) -> Self {
|
||||
SocketAddrV4::new((*value.ip()).into(), value.port())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SocketAddrV4> for core::net::SocketAddrV4 {
|
||||
#[inline]
|
||||
fn from(value: SocketAddrV4) -> Self {
|
||||
core::net::SocketAddrV4::new(value.ip().into(), value.port())
|
||||
}
|
||||
}
|
||||
|
||||
// V6
|
||||
|
||||
impl SocketAddrV6 {
|
||||
/// Constructs an IPv6 socket address from its IP and port components
|
||||
pub const fn new(address: Ipv6Addr, port: u16) -> Self {
|
||||
Self(address, port)
|
||||
}
|
||||
|
||||
/// Returns the IP address part of the value
|
||||
pub const fn ip(&self) -> Ipv6Addr {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns the port part of the value
|
||||
pub const fn port(&self) -> u16 {
|
||||
self.1
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SocketAddrV6 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}:{}", self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<core::net::SocketAddrV6> for SocketAddrV6 {
|
||||
#[inline]
|
||||
fn from(value: core::net::SocketAddrV6) -> Self {
|
||||
SocketAddrV6::new((*value.ip()).into(), value.port())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SocketAddrV6> for core::net::SocketAddrV6 {
|
||||
#[inline]
|
||||
fn from(value: SocketAddrV6) -> Self {
|
||||
core::net::SocketAddrV6::new(value.ip().into(), value.port(), 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// SocketAddr
|
||||
|
||||
impl SocketAddr {
|
||||
/// "Null" IPv4 address used when sender/recepient is not known or cannot be represented
|
||||
pub const NULL_V4: Self = Self::V4(SocketAddrV4::NULL);
|
||||
|
||||
/// Constructs a socket address from its IP and port components
|
||||
pub const fn new(address: IpAddr, port: u16) -> Self {
|
||||
match address {
|
||||
IpAddr::V4(address) => Self::V4(SocketAddrV4::new(address, port)),
|
||||
IpAddr::V6(address) => Self::V6(SocketAddrV6::new(address, port)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the IP address part of the value
|
||||
pub const fn ip(&self) -> IpAddr {
|
||||
match self {
|
||||
Self::V4(address) => IpAddr::V4(address.ip()),
|
||||
Self::V6(address) => IpAddr::V6(address.ip()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns [Some] if this is an IPv4 address, [None] otherwise
|
||||
pub fn into_ipv4(self) -> Option<SocketAddrV4> {
|
||||
match self {
|
||||
Self::V4(address) => Some(address),
|
||||
Self::V6(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the port part of the value
|
||||
pub const fn port(&self) -> u16 {
|
||||
match self {
|
||||
Self::V4(address) => address.port(),
|
||||
Self::V6(address) => address.port(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SocketAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::V4(address) => fmt::Display::fmt(address, f),
|
||||
Self::V6(address) => fmt::Display::fmt(address, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SocketAddr> for core::net::SocketAddr {
|
||||
#[inline]
|
||||
fn from(value: SocketAddr) -> Self {
|
||||
match value {
|
||||
SocketAddr::V4(address) => core::net::SocketAddr::V4(address.into()),
|
||||
SocketAddr::V6(address) => core::net::SocketAddr::V6(address.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<core::net::SocketAddr> for SocketAddr {
|
||||
#[inline]
|
||||
fn from(value: core::net::SocketAddr) -> Self {
|
||||
match value {
|
||||
core::net::SocketAddr::V4(address) => SocketAddr::V4(address.into()),
|
||||
core::net::SocketAddr::V6(address) => SocketAddr::V6(address.into()),
|
||||
}
|
||||
}
|
||||
}
|
122
lib/abi/src/net/types/subnet_addr.rs
Normal file
122
lib/abi/src/net/types/subnet_addr.rs
Normal file
@ -0,0 +1,122 @@
|
||||
//! Subnetwork address types
|
||||
use core::{fmt, str::FromStr};
|
||||
|
||||
use crate::{
|
||||
error::Error,
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
};
|
||||
|
||||
/// Describes an IPv4 subnetwork, e.g. 11.0.0.0/24
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[repr(C)]
|
||||
pub struct SubnetV4Addr {
|
||||
/// "Root" of the subnetwork, e.g. the "11.0.0.0" part in "11.0.0.0/24"
|
||||
pub root: Ipv4Addr,
|
||||
/// Number of masked bits, e.g. the "/24" part in "11.0.0.0/24"
|
||||
pub mask_bits: u32,
|
||||
mask: u32,
|
||||
}
|
||||
|
||||
/// Describes an IPv4/IPv6 subnetwork
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum SubnetAddr {
|
||||
/// IPv4 subnetwork
|
||||
V4(SubnetV4Addr),
|
||||
}
|
||||
|
||||
// SubnetV4
|
||||
|
||||
impl SubnetV4Addr {
|
||||
/// "Unspecified" subnetwork address: 0.0.0.0/0
|
||||
pub const UNSPECIFIED: Self = Self {
|
||||
root: Ipv4Addr::UNSPECIFIED,
|
||||
mask_bits: 0,
|
||||
mask: 0,
|
||||
};
|
||||
|
||||
/// Constructs a subnetwork from its components
|
||||
pub fn from_address_mask(address: Ipv4Addr, mask_bits: u8) -> Self {
|
||||
let root = u32::from(address);
|
||||
let mask = Self::make_mask(mask_bits);
|
||||
let root = root & Self::make_root_mask(mask_bits);
|
||||
|
||||
Self {
|
||||
root: Ipv4Addr::from(root),
|
||||
mask_bits: mask_bits as _,
|
||||
mask,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the subnet contains given address
|
||||
pub fn contains(&self, address: &Ipv4Addr) -> bool {
|
||||
let root = u32::from(self.root);
|
||||
let address = u32::from(*address);
|
||||
|
||||
(address & self.mask) == root
|
||||
}
|
||||
|
||||
fn make_root_mask(bits: u8) -> u32 {
|
||||
!((1 << (32 - bits)) - 1)
|
||||
}
|
||||
|
||||
fn make_mask(bits: u8) -> u32 {
|
||||
((1 << bits) - 1) << (32 - bits)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SubnetV4Addr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}/{}", self.root, self.mask_bits)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SubnetV4Addr {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut it = s.split('/');
|
||||
let network = it.next().ok_or(Error::InvalidArgument)?;
|
||||
let mask_bits = it.next().ok_or(Error::InvalidArgument)?;
|
||||
if it.next().is_some() {
|
||||
return Err(Error::InvalidArgument);
|
||||
}
|
||||
let network = Ipv4Addr::from_str(network).map_err(|_| Error::InvalidArgument)?;
|
||||
let mask_bits = u8::from_str(mask_bits).map_err(|_| Error::InvalidArgument)?;
|
||||
|
||||
Ok(Self::from_address_mask(network, mask_bits))
|
||||
}
|
||||
}
|
||||
|
||||
// Subnet
|
||||
|
||||
impl SubnetAddr {
|
||||
/// Returns `true` if the subnet contains given address
|
||||
pub fn contains(&self, address: &IpAddr) -> bool {
|
||||
match (self, address) {
|
||||
(Self::V4(subnet), IpAddr::V4(addr)) => subnet.contains(addr),
|
||||
(_, _) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SubnetAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::V4(subnet) => fmt::Display::fmt(subnet, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SubnetAddr {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if let Ok(v4) = SubnetV4Addr::from_str(s) {
|
||||
return Ok(Self::V4(v4));
|
||||
}
|
||||
Err(Error::InvalidArgument)
|
||||
}
|
||||
}
|
116
lib/abi/src/pass.rs
Normal file
116
lib/abi/src/pass.rs
Normal file
@ -0,0 +1,116 @@
|
||||
//! Interfaces for passing stuff between the kernel and the userspace
|
||||
|
||||
use core::ptr::NonNull;
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
/// Interface for a "writer", producing objects "owned" by the userspace.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The trait is marked unsafe as it is the implementor's concern that the produced pointers can
|
||||
/// actually hold the requested values and are properly aligned for that.
|
||||
pub unsafe trait Placer {
|
||||
/// Converts a kernel-space reference into a pointer that can be safely dereferenced in
|
||||
/// userspace
|
||||
fn place_ref<T: Place>(&mut self, r: &T) -> Result<NonNull<T::Output>, Error>;
|
||||
/// Converts a kernel-space slice reference into a slice pointer, elements of which are placed
|
||||
/// in safely-accessible memory by userspace
|
||||
fn place_slice<T: Place>(&mut self, r: &[T]) -> Result<NonNull<[T::Output]>, Error>;
|
||||
}
|
||||
|
||||
/// Interface for "transferring ownership" of an object to userspace.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The trait is marked unsafe because it may produce arbitrary pointer, so it's the implementor's
|
||||
/// concern that they are actually correct.
|
||||
pub unsafe trait Place {
|
||||
/// Conversion result type, must be safely accessible from userspace
|
||||
type Output: 'static;
|
||||
|
||||
/// Creates a userspace-reachable variant of the internal value
|
||||
fn place<P: Placer>(&self, placer: &mut P) -> Result<Self::Output, Error>;
|
||||
|
||||
/// Converts the internal value using [Place::place] and returns a safe userspace reference
|
||||
fn place_ref<P: Placer>(&self, placer: &mut P) -> Result<&'static Self::Output, Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let ptr = placer.place_ref(self)?;
|
||||
unsafe { Ok(ptr.as_ref()) }
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_primitive {
|
||||
($($ty:ty),+) => {
|
||||
$(
|
||||
unsafe impl $crate::pass::Place for $ty {
|
||||
type Output = $ty;
|
||||
|
||||
fn place<P: $crate::pass::Placer>(&self, _placer: &mut P) -> Result<$ty, Error> {
|
||||
Ok(*self)
|
||||
}
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
impl_primitive!(u8, u16, u32, u64, i8, i16, i32, i64, bool, char);
|
||||
|
||||
unsafe impl<'s> Place for &'s str {
|
||||
type Output = &'static str;
|
||||
|
||||
fn place<P: Placer>(&self, placer: &mut P) -> Result<Self::Output, Error> {
|
||||
let data = placer.place_slice(self.as_bytes())?;
|
||||
// Safety: safe, object passed in was already a proper &str
|
||||
Ok(unsafe { core::str::from_utf8_unchecked(data.as_ref()) })
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Place> Place for [T] {
|
||||
type Output = &'static [T::Output];
|
||||
|
||||
fn place<P: Placer>(&self, placer: &mut P) -> Result<Self::Output, Error> {
|
||||
let data = placer.place_slice(self)?;
|
||||
Ok(unsafe { data.as_ref() })
|
||||
}
|
||||
}
|
||||
|
||||
/// Automatically implements the [Place] interface for a struct, properly handling any nested
|
||||
/// references
|
||||
#[macro_export]
|
||||
macro_rules! impl_place_lifetime {
|
||||
(
|
||||
$( #[ $struct_meta:meta ] )*
|
||||
$struct_vis:vis struct $struct_name:ident<$struct_lifetime:lifetime> {
|
||||
$(
|
||||
$( #[ $field_meta:meta ] )*
|
||||
$field_vis:vis $field_name:ident: $field_ty:ty,
|
||||
)*
|
||||
}
|
||||
) => {
|
||||
$( #[$struct_meta] )*
|
||||
$struct_vis struct $struct_name<$struct_lifetime> {
|
||||
$(
|
||||
$( #[$field_meta] )*
|
||||
$field_vis $field_name: $field_ty
|
||||
),*
|
||||
}
|
||||
|
||||
unsafe impl<$struct_lifetime> $crate::pass::Place for $struct_name<$struct_lifetime> {
|
||||
type Output = $struct_name<'static>;
|
||||
|
||||
fn place<P: $crate::pass::Placer>(&self, placer: &mut P)
|
||||
-> Result<Self::Output, $crate::error::Error> {
|
||||
$(
|
||||
let $field_name = self.$field_name.place(placer)?;
|
||||
)*
|
||||
|
||||
Ok($struct_name {
|
||||
$($field_name),*
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
11
lib/abi/src/path/mod.rs
Normal file
11
lib/abi/src/path/mod.rs
Normal file
@ -0,0 +1,11 @@
|
||||
//! Filesystem path definitions and manipulation functions
|
||||
|
||||
#![allow(missing_docs)]
|
||||
|
||||
mod path;
|
||||
#[cfg(feature = "alloc")]
|
||||
mod path_buf;
|
||||
|
||||
pub use path::Path;
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use path_buf::PathBuf;
|
119
lib/abi/src/path/path.rs
Normal file
119
lib/abi/src/path/path.rs
Normal file
@ -0,0 +1,119 @@
|
||||
use core::ffi::CStr;
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct Path(str);
|
||||
|
||||
impl Path {
|
||||
pub const SEPARATOR: char = '/';
|
||||
pub const SEPARATOR_STR: &'static str = "/";
|
||||
|
||||
/// Parent directory path element
|
||||
pub const PARENT_NAME: &'static str = "..";
|
||||
/// Path element that refers to itself
|
||||
pub const SELF_NAME: &'static str = ".";
|
||||
|
||||
pub fn from_str(s: &str) -> &Self {
|
||||
unsafe { core::mem::transmute(s) }
|
||||
}
|
||||
|
||||
pub fn from_c_str(s: &CStr) -> Result<&Self, Error> {
|
||||
s.to_str()
|
||||
.map_err(|_| Error::InvalidArgument)
|
||||
.map(Path::from_str)
|
||||
}
|
||||
|
||||
pub fn empty() -> &'static Self {
|
||||
Self::from_str("")
|
||||
}
|
||||
|
||||
pub fn trim_start_separators(&self) -> &Self {
|
||||
Self::from_str(self.0.trim_start_matches(Self::SEPARATOR))
|
||||
}
|
||||
|
||||
pub fn trim_end_separators(&self) -> &Self {
|
||||
Self::from_str(self.0.trim_end_matches(Self::SEPARATOR))
|
||||
}
|
||||
|
||||
pub fn display(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn split_right(&self) -> (&Path, &str) {
|
||||
if let Some((left, right)) = self
|
||||
.0
|
||||
.trim_end_matches(Self::SEPARATOR)
|
||||
.rsplit_once(Self::SEPARATOR)
|
||||
{
|
||||
(
|
||||
Path::from_str(left.trim_end_matches(Self::SEPARATOR)),
|
||||
right,
|
||||
)
|
||||
} else {
|
||||
(Path::empty(), &self.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn split_left(&self) -> (&str, &Path) {
|
||||
if let Some((left, right)) = self.0.split_once(Self::SEPARATOR) {
|
||||
(
|
||||
left,
|
||||
Path::from_str(right.trim_start_matches(Self::SEPARATOR)),
|
||||
)
|
||||
} else {
|
||||
(&self.0, Path::empty())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_absolute(&self) -> bool {
|
||||
self.0.starts_with(Self::SEPARATOR)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_ptr(&self) -> *const u8 {
|
||||
self.0.as_ptr()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for Path {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Path> for Path {
|
||||
fn as_ref(&self) -> &Path {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Path> for str {
|
||||
fn as_ref(&self) -> &Path {
|
||||
Path::from_str(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl AsRef<Path> for alloc::string::String {
|
||||
fn as_ref(&self) -> &Path {
|
||||
self.as_str().as_ref()
|
||||
}
|
||||
}
|
71
lib/abi/src/path/path_buf.rs
Normal file
71
lib/abi/src/path/path_buf.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use crate::path::Path;
|
||||
use alloc::{
|
||||
borrow::{Borrow, ToOwned},
|
||||
string::String,
|
||||
};
|
||||
use core::ops::Deref;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct PathBuf(String);
|
||||
|
||||
impl PathBuf {
|
||||
pub fn new() -> Self {
|
||||
Self(String::new())
|
||||
}
|
||||
|
||||
pub fn from_str(s: &str) -> Self {
|
||||
Self(String::from(s))
|
||||
}
|
||||
|
||||
pub fn push<P: AsRef<Path>>(&mut self, _path: P) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn join<P: AsRef<Path>>(&mut self, _path: P) -> Self {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn display(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for PathBuf {
|
||||
type Target = Path;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
Path::from_str(self.0.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Path> for PathBuf {
|
||||
fn as_ref(&self) -> &Path {
|
||||
Path::from_str(self.0.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Path> for PathBuf {
|
||||
fn from(value: &Path) -> Self {
|
||||
Self::from_str(value.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for PathBuf {
|
||||
fn from(value: &str) -> Self {
|
||||
Self::from_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOwned for Path {
|
||||
type Owned = PathBuf;
|
||||
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
PathBuf::from(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<Path> for PathBuf {
|
||||
fn borrow(&self) -> &Path {
|
||||
Path::from_str(self.0.as_str())
|
||||
}
|
||||
}
|
63
lib/abi/src/process/exit.rs
Normal file
63
lib/abi/src/process/exit.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use core::num::NonZeroI32;
|
||||
|
||||
use abi_lib::SyscallRegister;
|
||||
|
||||
use super::signal::Signal;
|
||||
|
||||
/// Code signalled by a process when it finishes or is terminated by a signal
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(C)]
|
||||
pub enum ExitCode {
|
||||
/// Process exited normally, either with an error or with a 0
|
||||
Exited(i32),
|
||||
/// Process was killed by a signal
|
||||
BySignal(Signal),
|
||||
}
|
||||
|
||||
impl ExitCode {
|
||||
/// Returned when a process has exited successfully
|
||||
pub const SUCCESS: Self = Self::Exited(0);
|
||||
|
||||
/// Returns `true` if this ExitCode signals a successful exit
|
||||
pub const fn is_success(self) -> bool {
|
||||
matches!(self, Self::Exited(0))
|
||||
}
|
||||
|
||||
/// Returns [Some] if the process exited with an error or was killed by a signal
|
||||
pub fn as_failure(self) -> Option<NonZeroI32> {
|
||||
match self {
|
||||
Self::Exited(0) => None,
|
||||
Self::Exited(n) => Some(unsafe { NonZeroI32::new_unchecked(n) }),
|
||||
Self::BySignal(sig) => {
|
||||
Some(unsafe { NonZeroI32::new_unchecked(u32::from(sig) as i32) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for ExitCode {
|
||||
fn from(value: i32) -> Self {
|
||||
match value {
|
||||
10000.. => Self::BySignal(Signal::try_from((value - 10000) as u32).unwrap()),
|
||||
0..=512 => Self::Exited(value - 255),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SyscallRegister for ExitCode {
|
||||
fn into_syscall_register(self) -> usize {
|
||||
match self {
|
||||
Self::Exited(value) => (value + 255) as usize,
|
||||
Self::BySignal(value) => value.into_syscall_register() + 10000,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_syscall_register(value: usize) -> Self {
|
||||
match value {
|
||||
10000.. => Self::BySignal(Signal::from_syscall_register(value - 10000)),
|
||||
0..=512 => Self::Exited(value as i32 - 255),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
55
lib/abi/src/process/mod.rs
Normal file
55
lib/abi/src/process/mod.rs
Normal file
@ -0,0 +1,55 @@
|
||||
//! Data structures for process management
|
||||
|
||||
use core::{fmt, time::Duration};
|
||||
|
||||
use crate::{impl_place_lifetime, io::FileMode};
|
||||
|
||||
mod exit;
|
||||
mod signal;
|
||||
mod spawn;
|
||||
|
||||
pub use crate::generated::{ProcessId, ThreadId};
|
||||
pub use exit::ExitCode;
|
||||
pub use signal::{Signal, SignalEntryData};
|
||||
pub use spawn::{ExecveOptions, SpawnOption, SpawnOptions, ThreadSpawnOptions};
|
||||
|
||||
/// Describes a single mutex operation
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub enum MutexOperation {
|
||||
/// Waits on the mutex object until it is different from "compare value"
|
||||
Wait(u32, Option<Duration>),
|
||||
/// Wakes a single mutex-waiting thread
|
||||
Wake,
|
||||
/// Wakes all threads waiting on the mutex
|
||||
WakeAll,
|
||||
}
|
||||
|
||||
/// Provides some amount of process-related information
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ProcessInfoElement {
|
||||
/// Mask applied to file modes when creating new files/directories
|
||||
Umask(FileMode),
|
||||
}
|
||||
|
||||
// TODO not sure if I really need #[repr(C)] ABI here
|
||||
impl_place_lifetime! {
|
||||
#[doc = "Argument struct passed from the kernel to a spawned process"]
|
||||
#[derive(Debug)]
|
||||
pub struct ProgramArgumentInner<'a> {
|
||||
#[doc = "Argument list"]
|
||||
pub args: &'a [&'a str],
|
||||
#[doc = "List of KEY=VALUE environment variable pairs"]
|
||||
pub env: &'a [&'a str],
|
||||
}
|
||||
}
|
||||
|
||||
impl ProcessId {
|
||||
pub const ZERO: Self = Self(0);
|
||||
}
|
||||
|
||||
impl fmt::Display for ProcessId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "<Process {}>", self.0)
|
||||
}
|
||||
}
|
13
lib/abi/src/process/signal.rs
Normal file
13
lib/abi/src/process/signal.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use crate::arch::SavedFrame;
|
||||
|
||||
pub use crate::generated::Signal;
|
||||
|
||||
/// Data provided by the kernel to signal entry function
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct SignalEntryData {
|
||||
/// Which signal was issued
|
||||
pub signal: Signal,
|
||||
/// Saved frame of the context that was interrupted
|
||||
pub frame: SavedFrame,
|
||||
}
|
56
lib/abi/src/process/spawn.rs
Normal file
56
lib/abi/src/process/spawn.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use crate::io::RawFd;
|
||||
|
||||
use super::ProcessId;
|
||||
|
||||
/// Defines an optional argument for controlling process creation
|
||||
#[derive(Debug)]
|
||||
pub enum SpawnOption {
|
||||
/// Indicates a new process should inherit a file descriptor from its creator
|
||||
InheritFile {
|
||||
/// FD on the creator side
|
||||
source: RawFd,
|
||||
/// What FD number should be used in the child
|
||||
child: RawFd,
|
||||
},
|
||||
/// The new process should be placed in the specified group
|
||||
SetProcessGroup(ProcessId),
|
||||
/// Gain terminal control for the given FD
|
||||
GainTerminal(RawFd),
|
||||
}
|
||||
|
||||
/// Controls how processes are created
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct SpawnOptions<'a> {
|
||||
/// Creator-relative path to the binary to execute
|
||||
pub program: &'a str,
|
||||
/// Arguments to the child process
|
||||
pub arguments: &'a [&'a str],
|
||||
/// Environment for the child process
|
||||
pub environment: &'a [&'a str],
|
||||
/// Optional arguments to specify details of the creation
|
||||
pub optional: &'a [SpawnOption],
|
||||
}
|
||||
|
||||
/// Legacy execve(2) arguments
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct ExecveOptions<'a> {
|
||||
pub program: &'a str,
|
||||
pub arguments: &'a [&'a str],
|
||||
pub environment: &'a [&'a str],
|
||||
}
|
||||
|
||||
/// Controls how threads are spawned within a process
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct ThreadSpawnOptions {
|
||||
/// Thread entry function
|
||||
pub entry: extern "C" fn(u64) -> !,
|
||||
/// Thread argument value
|
||||
pub argument: u64,
|
||||
/// Thread stack pointer. NOTE: it's the caller's responsibility to allocate a proper stack for
|
||||
/// the thread.
|
||||
pub stack_top: usize,
|
||||
}
|
136
lib/abi/src/syscall.rs
Normal file
136
lib/abi/src/syscall.rs
Normal file
@ -0,0 +1,136 @@
|
||||
//! System call function definitions
|
||||
use crate::primitive_enum;
|
||||
|
||||
primitive_enum!(
|
||||
#[doc = "Describes a system call issued to the kernel"]
|
||||
pub enum SyscallFunction: usize {
|
||||
#[doc = "Suspend the caller task for some time"]
|
||||
Nanosleep = 1,
|
||||
#[doc = "Map a virtual memory region"]
|
||||
MapMemory = 2,
|
||||
#[doc = "Release a virtual memory region"]
|
||||
UnmapMemory = 3,
|
||||
#[doc = "Write data to a file descriptor"]
|
||||
Write = 4,
|
||||
#[doc = "Read data from a file descriptor"]
|
||||
Read = 5,
|
||||
#[doc = "Open a file"]
|
||||
Open = 6,
|
||||
#[doc = "Close a file descriptor"]
|
||||
Close = 7,
|
||||
#[doc = "Open a directory for reading"]
|
||||
OpenDirectory = 8,
|
||||
#[doc = "Read entries from a directory descriptor"]
|
||||
ReadDirectory = 9,
|
||||
#[doc = "Create a directory"]
|
||||
CreateDirectory = 10,
|
||||
#[doc = "Remove a file"]
|
||||
Remove = 11,
|
||||
#[doc = "Remove a directory"]
|
||||
RemoveDirectory = 12,
|
||||
#[doc = "Retrieves metadata for a filesystem entry"]
|
||||
GetMetadata = 13,
|
||||
#[doc = "Seek a file descriptor to the specified position"]
|
||||
Seek = 14,
|
||||
#[doc = "Perform a device-specific request"]
|
||||
DeviceRequest = 15,
|
||||
#[doc = "Create an anonymous pipe"]
|
||||
CreatePipe = 16,
|
||||
#[doc = "Create a file descriptor poll channel"]
|
||||
CreatePoll = 17,
|
||||
#[doc = "Add/remove/modify poll channel entries of interest"]
|
||||
PollControl = 18,
|
||||
#[doc = "Poll a channel for descriptor readiness"]
|
||||
PollWait = 19,
|
||||
|
||||
#[doc = "Create or get an existing messaging channel"]
|
||||
OpenChannel = 20,
|
||||
#[doc = "Receive a message from channel subscription"]
|
||||
ReceiveMessage = 21,
|
||||
#[doc = "Send a message to a channel"]
|
||||
SendMessage = 22,
|
||||
|
||||
#[doc = "Creates a buffer of shared memory and returns a FD to it"]
|
||||
CreateSharedMemory = 23,
|
||||
|
||||
#[doc = "Creates a pair of pseudo-terminal master/slave"]
|
||||
CreatePty = 24,
|
||||
|
||||
#[doc = "Clones a file descriptor"]
|
||||
CloneFd = 25,
|
||||
|
||||
#[doc = "Changes file metadata"]
|
||||
UpdateMetadata = 26,
|
||||
|
||||
#[doc = "Creates a event-generator file descriptor"]
|
||||
CreateTimer = 27,
|
||||
|
||||
#[doc = "Create and start a new process"]
|
||||
SpawnProcess = 40,
|
||||
#[doc = "Wait for a process to exit"]
|
||||
WaitProcess = 41,
|
||||
#[doc = "Get the caller process ID"]
|
||||
GetPid = 42,
|
||||
#[doc = "Get the caller process session ID"]
|
||||
GetSessionId = 43,
|
||||
#[doc = "Get the caller process group ID"]
|
||||
GetProcessGroupId = 44,
|
||||
#[doc = "Put the caller process into given group ID"]
|
||||
SetProcessGroupId = 45,
|
||||
#[doc = "Start a new session and set the caller process as its leader"]
|
||||
StartSession = 46,
|
||||
#[doc = "Create and start a new thread within the caller's process"]
|
||||
SpawnThread = 47,
|
||||
#[doc = "Mutex operation: wait, wake, etc."]
|
||||
Mutex = 48,
|
||||
|
||||
#[doc = "Send a signal to a process"]
|
||||
SendSignal = 50,
|
||||
#[doc = "Setup a signal entry for the caller process"]
|
||||
SetSignalEntry = 51,
|
||||
#[doc = "Exit from a syscall handler"]
|
||||
ExitSignal = 52,
|
||||
#[doc = "Terminate the caller thread"]
|
||||
ExitThread = 53,
|
||||
#[doc = "Terminate the caller process"]
|
||||
ExitProcess = 54,
|
||||
|
||||
// C compat
|
||||
#[doc = "Fork a process, creating an identical copy of its address space (C compat)"]
|
||||
Fork = 55,
|
||||
#[doc = "Replace the process address space with a new loaded program"]
|
||||
Exec = 56,
|
||||
|
||||
#[doc = "Get information about the calling process"]
|
||||
GetProcessInfo = 57,
|
||||
#[doc = "Update information about the calling process"]
|
||||
SetProcessInfo = 58,
|
||||
|
||||
#[doc = "Mount a filesystem"]
|
||||
Mount = 101,
|
||||
#[doc = "Unmount a filesystem"]
|
||||
Unmount = 102,
|
||||
|
||||
#[doc = "Print a message to the kernel's debug trace output"]
|
||||
DebugTrace = 128,
|
||||
#[doc = "Fetches bytes of random data"]
|
||||
GetRandom = 129,
|
||||
#[doc = "Retrieves system information"]
|
||||
GetSystemInfo = 130,
|
||||
|
||||
#[doc = "Bind a socket for listening"]
|
||||
BindSocket = 256,
|
||||
#[doc = "Connect a socket to a remote host"]
|
||||
ConnectSocket = 257,
|
||||
#[doc = "Send a message to a socket"]
|
||||
SendTo = 258,
|
||||
#[doc = "Receive a message from a socket"]
|
||||
ReceiveFrom = 259,
|
||||
#[doc = "Get socket option value"]
|
||||
GetSocketOption = 260,
|
||||
#[doc = "Update socket option value"]
|
||||
SetSocketOption = 261,
|
||||
#[doc = "Accept an incoming stream connection"]
|
||||
Accept = 262,
|
||||
}
|
||||
);
|
21
lib/abi/src/system.rs
Normal file
21
lib/abi/src/system.rs
Normal file
@ -0,0 +1,21 @@
|
||||
//! System-related data structures
|
||||
|
||||
/// Describes system memory usage information
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct SystemMemoryStats {
|
||||
/// Total number of pages in the system
|
||||
pub total_usable_pages: usize,
|
||||
/// Amount of used/allocated memory
|
||||
pub allocated_pages: usize,
|
||||
/// Amount of usable/free memory
|
||||
pub free_pages: usize,
|
||||
/// Single page size
|
||||
pub page_size: usize,
|
||||
}
|
||||
|
||||
/// Describes a piece of system information
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SystemInfo {
|
||||
/// Memory usage stats
|
||||
MemoryStats(SystemMemoryStats),
|
||||
}
|
95
lib/abi/src/util.rs
Normal file
95
lib/abi/src/util.rs
Normal file
@ -0,0 +1,95 @@
|
||||
//! Misc utilities for the ABI crate
|
||||
|
||||
use crate::error::Error;
|
||||
use core::{fmt, str::FromStr};
|
||||
|
||||
/// Helper struct to hold a fixed-size string of UTF-8 characters
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct FixedString<const N: usize> {
|
||||
data: [u8; N],
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl<const N: usize> FixedString<N> {
|
||||
/// Returns an empty [FixedString]
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
data: [0; N],
|
||||
len: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the length of the string
|
||||
pub const fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
|
||||
/// Returns `true` if the string is empty
|
||||
pub const fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
|
||||
/// Returns the underlying byte data of the string
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&self.data[..self.len]
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> fmt::Debug for FixedString<N> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(self.as_ref(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> AsRef<str> for FixedString<N> {
|
||||
fn as_ref(&self) -> &str {
|
||||
// Safety: totally safe, the FixedString can only be constructed from a &str or be empty
|
||||
unsafe { core::str::from_utf8_unchecked(&self.data[..self.len]) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> FromStr for FixedString<N> {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.len() >= N {
|
||||
return Err(Error::InvalidArgument);
|
||||
}
|
||||
let len = s.len();
|
||||
let mut data = [0; N];
|
||||
data[..len].copy_from_slice(s.as_bytes());
|
||||
Ok(Self { data, len })
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> PartialEq<&str> for FixedString<N> {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
&self.data[..self.len] == other.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
/// Reinterprets a `T` structure as a slice of its bytes
|
||||
#[cfg(feature = "bytemuck")]
|
||||
pub fn bytes_of<T: bytemuck::Pod>(data: &T) -> &[u8] {
|
||||
bytemuck::bytes_of(data)
|
||||
}
|
||||
|
||||
/// Reinterprets a slice of bytes as a `T` structure reference
|
||||
#[cfg(feature = "bytemuck")]
|
||||
pub fn from_bytes<T: bytemuck::Pod>(bytes: &[u8]) -> &T {
|
||||
bytemuck::from_bytes(bytes)
|
||||
}
|
||||
|
||||
/// Reinterprets a `T` structure as a slice of its bytes
|
||||
#[cfg(not(feature = "bytemuck"))]
|
||||
pub fn bytes_of<T>(data: &T) -> &[u8] {
|
||||
unsafe { core::slice::from_raw_parts(data as *const T as *const u8, core::mem::size_of::<T>()) }
|
||||
}
|
||||
|
||||
/// Reinterprets a slice of bytes as a `T` structure reference
|
||||
#[cfg(not(feature = "bytemuck"))]
|
||||
pub fn from_bytes<T>(bytes: &[u8]) -> &T {
|
||||
// TODO align check
|
||||
assert_eq!(bytes.len(), core::mem::size_of::<T>());
|
||||
unsafe { &*(&bytes[0] as *const u8 as *const T) }
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user