oc-wasm-safe 0.4.0

Safe but low-level wrappers around the OC-Wasm system call interface
Documentation
use crate::panic_or_trap;
use core::fmt::{Display, Formatter};

/// The errors that a system call can return.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Error {
	/// A CBOR data item is invalid CBOR or encodes an unsupported type or value.
	CborDecode,

	/// A buffer provided for the syscall to write into is too short.
	BufferTooShort,

	/// A component UUID refers to a component that does not exist or cannot be accessed.
	NoSuchComponent,

	/// A method invocation refers to a method that does not exist.
	NoSuchMethod,

	/// The parameters are incorrect in a way that does not have a more specific error code.
	BadParameters,

	/// A queue is full.
	QueueFull,

	/// A queue is empty.
	QueueEmpty,

	/// A descriptor is negative or not open.
	BadDescriptor,

	/// There are too many open descriptors.
	TooManyDescriptors,

	/// The operation failed for an otherwise unspecified reason.
	Other,

	/// A system call returned an error code that does not correspond to any known value.
	///
	/// It is likely that OC-Wasm has been updated to a version which adds new error codes, and
	/// OC-Wasm-Safe has not been updated to match.
	Unknown,
}

impl Error {
	/// Returns a string describing the error.
	#[must_use = "This function is only useful for its return value"]
	pub fn as_str(self) -> &'static str {
		match self {
			Self::CborDecode => "CBOR decode error",
			Self::BufferTooShort => "Buffer too short",
			Self::NoSuchComponent => "No such component",
			Self::NoSuchMethod => "No such method",
			Self::BadParameters => "Bad parameters",
			Self::QueueFull => "Queue full",
			Self::QueueEmpty => "Queue empty",
			Self::BadDescriptor => "Bad descriptor",
			Self::TooManyDescriptors => "Too many descriptors",
			Self::Other => "Other error",
			Self::Unknown => "Unknown error",
		}
	}
}

impl Display for Error {
	fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
		f.write_str(self.as_str())
	}
}

impl Error {
	/// Checks a system call return value of type `isize` for an error value.
	///
	/// Returns a `Result` containing an `Error` if the value is negative, or the original value if
	/// it was nonnegative.
	///
	/// # Errors
	/// This function fails if the parameter is negative, decoding the represented error code.
	///
	/// # Panics
	/// This function panics if the syscall error code is `MemoryFault` or `StringDecode`. These
	/// syscall errors should be impossible in safe code because the type system prohibits them:
	/// `MemoryFault` should be impossible because all memory regions are taken as slices which are
	/// always valid, and `StringDecode` should be impossible because all strings are taken as
	/// string-slices (`&str`) which are always valid UTF-8.
	pub fn from_isize(value: isize) -> Result<usize> {
		match value {
			-1 => panic_or_trap!("Memory fault"), // Impossible due to memory safety
			-2 => Err(Self::CborDecode),
			-3 => panic_or_trap!("String decode error"), // Impossible due to type safety of &str
			-4 => Err(Self::BufferTooShort),
			-5 => Err(Self::NoSuchComponent),
			-6 => Err(Self::NoSuchMethod),
			-7 => Err(Self::BadParameters),
			-8 => Err(Self::QueueFull),
			-9 => Err(Self::QueueEmpty),
			-10 => Err(Self::BadDescriptor),
			-11 => Err(Self::TooManyDescriptors),
			-12 => Err(Self::Other),
			x if x < 0 => Err(Self::Unknown),
			_ => {
				// Cast from isize to usize is safe because the match arm verifies that x ≥ 0.
				#[allow(clippy::cast_sign_loss)]
				Ok(value as usize)
			}
		}
	}

	/// Checks a system call return value of type `i32` for an error value.
	///
	/// Returns a `Result` containing an `Error` if the value is negative, or the original value if
	/// it was nonnegative.
	///
	/// # Errors
	/// This function fails if the parameter is negative, decoding the represented error code.
	///
	/// # Panics
	/// This function panics if the syscall error code is `MemoryFault` or `StringDecode`. These
	/// syscall errors should be impossible in safe code because the type system prohibits them:
	/// `MemoryFault` should be impossible because all memory regions are taken as slices which are
	/// always valid, and `StringDecode` should be impossible because all strings are taken as
	/// string-slices (`&str`) which are always valid UTF-8.
	pub fn from_i32(value: i32) -> Result<u32> {
		// Cast from i32 to isize is safe because Wasm is a 32-bit target (or more), so isize is at
		// least 32 bits. Cast from usize back to u32 is safe because the value was originally an
		// i32, and from_isize returns an unsigned value.
		#[allow(clippy::cast_possible_truncation)]
		Ok(Self::from_isize(value as isize)? as u32)
	}
}

#[cfg(feature = "std")]
impl std::error::Error for Error {}

pub type Result<T> = core::result::Result<T, Error>;