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}