oc_wasm_helpers/
lib.rs

1//! This crate provides a collection of helpers and utilities used by other OC-Wasm high-level
2//! APIs. It is not a useful crate for application developers, unless you are developing an API for
3//! a new mod that doesn’t have one yet.
4//!
5//! # Features
6//! * The `alloc` feature enables APIs that require dynamic memory allocation.
7//!
8//! # Important
9//! You *must* depend on [`oc-wasm-futures`](https://gitlab.com/Hawk777/oc-wasm-futures) with the
10//! `proper-waker` feature in your own application if your chosen executor requires the
11//! `proper-waker` feature.
12
13#![cfg_attr(not(feature = "std"), no_std)]
14#![warn(
15	// Turn on extra language lints.
16	future_incompatible,
17	missing_abi,
18	nonstandard_style,
19	rust_2018_idioms,
20	// Disabled due to <https://github.com/rust-lang/rust/issues/69952>.
21	// single_use_lifetimes,
22	trivial_casts,
23	trivial_numeric_casts,
24	unused,
25	unused_crate_dependencies,
26	unused_import_braces,
27	unused_lifetimes,
28	unused_qualifications,
29
30	// Turn on extra Rustdoc lints.
31	rustdoc::all,
32
33	// Turn on extra Clippy lints.
34	clippy::cargo,
35	clippy::pedantic,
36)]
37// I’m not a big fan of this style, and it sometimes generates larger code.
38#![allow(clippy::option_if_let_else)]
39// Nope, tabs thanks.
40#![allow(clippy::tabs_in_doc_comments)]
41
42#[cfg(feature = "alloc")]
43extern crate alloc;
44
45pub mod error;
46pub mod fluid;
47pub mod inventory;
48pub mod map_decoder;
49pub mod sides;
50
51#[cfg(feature = "alloc")]
52use minicbor::Decode;
53
54/// A component that can be given an [`Invoker`](oc_wasm_safe::component::Invoker) and a byte
55/// buffer in order to access its methods.
56pub trait Lockable<'invoker, 'buffer, B: oc_wasm_futures::invoke::Buffer> {
57	/// The type obtained when locking the component.
58	type Locked;
59
60	/// Locks the component so methods can be invoked on it.
61	///
62	/// The [`Invoker`](oc_wasm_safe::component::Invoker) and a scratch buffer must be provided.
63	/// They are released and can be reused once the locked value is dropped.
64	#[must_use = "This function is only useful for its return value"]
65	fn lock(
66		&self,
67		invoker: &'invoker mut oc_wasm_safe::component::Invoker,
68		buffer: &'buffer mut B,
69	) -> Self::Locked;
70}
71
72/// Decodes a CBOR map with one-based integer keys into a vector.
73///
74/// Each element may have a mapping function applied to convert it from its raw decoded type into
75/// its final result type, if desired; this avoids the need to allocate a second vector to collect
76/// the results of the conversion afterwards.
77///
78/// # Errors
79/// This function fails if the map contains duplicate keys, non-natural keys, or keys outside legal
80/// bounds.
81#[cfg(feature = "alloc")]
82pub fn decode_one_based_map_as_vector<
83	'buffer,
84	Context,
85	DecodedType: Decode<'buffer, Context>,
86	ResultType: From<DecodedType>,
87>(
88	d: &mut minicbor::Decoder<'buffer>,
89	context: &mut Context,
90) -> Result<alloc::vec::Vec<ResultType>, minicbor::decode::Error> {
91	use alloc::vec;
92	use alloc::vec::Vec;
93
94	/// A fixed-sized vector, some of whose elements are initialized and some of which are not.
95	struct PartialArray<T> {
96		/// The storage.
97		///
98		/// The length of this vector is zero; the data is stored in the extra capacity (which is
99		/// the expected length).
100		storage: Vec<T>,
101
102		/// A vector indicating which elements have been initialized yet.
103		initialized: Vec<bool>,
104	}
105
106	impl<T> PartialArray<T> {
107		/// Creates a new `PartialArray` of the specified length.
108		pub fn new(len: usize) -> Self {
109			Self {
110				storage: Vec::with_capacity(len),
111				initialized: vec![false; len],
112			}
113		}
114
115		/// Places a new element into the vector in an empty cell.
116		///
117		/// If the index is not initialized, it becomes initialized with the provided element and
118		/// `true` is returned. If the index is already initialized, nothing happens and `false` is
119		/// returned.
120		pub fn set(&mut self, index: usize, value: T) -> bool {
121			if core::mem::replace(&mut self.initialized[index], true) {
122				false
123			} else {
124				// SAFETY: We just verified that the indexth element is uninitialized. The fact
125				// that initialized[index] did not panic also means that index is within bounds
126				// (because storage.capacity=initialized.len).
127				unsafe { self.storage.as_mut_ptr().add(index).write(value) };
128				true
129			}
130		}
131
132		/// Returns the array.
133		///
134		/// If all positions are initialized, `Some` is returned. If any position is not
135		/// initialized, `None` is returned.
136		pub fn finish(mut self) -> Option<Vec<T>> {
137			if self.initialized.iter().all(|&x| x) {
138				let mut storage = core::mem::take(&mut self.storage);
139				// SAFETY: We just verified that all positions are initialized.
140				unsafe { storage.set_len(storage.capacity()) };
141				Some(storage)
142			} else {
143				None
144			}
145		}
146	}
147
148	impl<T> Drop for PartialArray<T> {
149		fn drop(&mut self) {
150			for i in 0..self.storage.capacity() {
151				if self.initialized[i] {
152					// SAFETY: We just verified that the position is initialized.
153					unsafe { self.storage.as_mut_ptr().add(i).read() };
154					// Drop the value returned by read().
155				}
156			}
157		}
158	}
159
160	let len = d.map()?;
161	// The CBOR fits in memory, so it must be <2³² elements.
162	#[allow(clippy::cast_possible_truncation)]
163	let len = len.ok_or_else(|| {
164		minicbor::decode::Error::message("indefinite-length maps are not supported")
165	})? as usize;
166	let mut data = PartialArray::<ResultType>::new(len);
167	for _ in 0..len {
168		let index = d.u32()? as usize;
169		if index == 0 || index > len {
170			return Err(minicbor::decode::Error::message("invalid map index"));
171		}
172		let value = d.decode_with::<Context, DecodedType>(context)?;
173		if !data.set(index - 1, value.into()) {
174			return Err(minicbor::decode::Error::message("duplicate map key"));
175		}
176	}
177	if let Some(data) = data.finish() {
178		Ok(data)
179	} else {
180		Err(minicbor::decode::Error::message("missing map key"))
181	}
182}