Skip to main content

corevm_guest/
transactions.rs

1#![allow(clippy::missing_safety_doc)]
2
3use crate::{c, flags::RecvTransaction, GuestId, RecvTransactionOutcome};
4use core::mem::size_of_val;
5
6/// Opaque transaction failure.
7#[derive(Debug)]
8pub struct TransactionFailed;
9
10impl core::fmt::Display for TransactionFailed {
11	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
12		f.write_str("Transaction failed")
13	}
14}
15
16trait RetToResult {
17	/// Convert host-call return value to result.
18	fn into_result(self) -> Result<(), TransactionFailed>;
19}
20
21impl RetToResult for u64 {
22	fn into_result(self) -> Result<(), TransactionFailed> {
23		if self == 0 {
24			Ok(())
25		} else {
26			Err(TransactionFailed)
27		}
28	}
29}
30
31/// Initiates a transaction with `target` providing the `payload`.
32///
33/// Blocks until the target guest calls [`recv_transaction`].
34///
35/// Returns `Ok(())` if both `initiate_transaction` and `recv_transaction` call succeed. Returns
36/// `Err(TransactionFailed)` otherwise.
37pub fn initiate_transaction(target: GuestId, payload: &[u8]) -> Result<(), TransactionFailed> {
38	let address = payload.as_ptr() as u64;
39	let len = size_of_val(payload) as u64;
40	unsafe { c::initiate_transaction(u64::from(target), address, len) }.into_result()
41}
42
43/// Receives a transaction from the transaction initiator.
44///
45/// Returns the payload and the guest ID of the initiator or `None` if there is no pending
46/// transaction.
47///
48/// This is a non-blocking variant of [`recv_transaction`].
49#[cfg(feature = "alloc")]
50pub fn try_recv_transaction() -> Option<(alloc::vec::Vec<u8>, GuestId)> {
51	let ret = unsafe { c::recv_transaction(0, 0, RecvTransaction::NON_BLOCKING.bits()) };
52	let (len, source) = RecvTransactionOutcome::from_reg(ret).into_inner()?;
53	let mut buf = alloc::vec::Vec::with_capacity(len as usize);
54	unsafe {
55		c::recv_transaction(
56			buf.as_mut_ptr() as u64,
57			u64::from(len),
58			RecvTransaction::NON_BLOCKING.bits(),
59		)
60	};
61	unsafe { buf.set_len(len as usize) };
62	Some((buf, source))
63}
64
65/// Receives a transaction from the transaction initiator.
66///
67/// Returns the payload and the guest ID of the initiator.
68///
69/// Blocks until some transaction against this guest becomes pending.
70/// For a non-blocking variant see [`try_recv_transaction`].
71#[cfg(feature = "alloc")]
72pub fn recv_transaction() -> (alloc::vec::Vec<u8>, GuestId) {
73	let ret = unsafe { c::recv_transaction(0, 0, 0) };
74	let (len, source) = RecvTransactionOutcome::from_reg(ret)
75		.into_inner()
76		.expect("The call is blocking");
77	let mut buf = alloc::vec::Vec::with_capacity(len as usize);
78	unsafe { c::recv_transaction(buf.as_mut_ptr() as u64, u64::from(len), 0) };
79	unsafe { buf.set_len(len as usize) };
80	(buf, source)
81}
82
83/// Receives a transaction from the transaction initiator.
84///
85/// This is a non-blocking variant of [`recv_transaction_into`].
86///
87/// # Return value
88///
89/// - Returns `None` if there is no pending transaction.
90/// - Returns `Some(Err(n))` where `n` is the payload size if the buffer is too small.
91/// - On success returns `Some(Ok((n, guest_id)))` where `n` is the payload size and `guest_id` is
92///   the guest ID of the initiator.
93#[inline]
94pub fn try_recv_transaction_into(buf: &mut [u8]) -> Option<Result<(usize, GuestId), usize>> {
95	let ret = unsafe {
96		c::recv_transaction(
97			buf.as_mut_ptr() as u64,
98			buf.len() as u64,
99			RecvTransaction::NON_BLOCKING.bits(),
100		)
101	};
102	let (len, source) = RecvTransactionOutcome::from_reg(ret).into_inner()?;
103	if len > buf.len() as u32 {
104		return Some(Err(len as usize));
105	}
106	Some(Ok((len as usize, source)))
107}
108
109/// Receives a transaction from the transaction initiator.
110///
111/// Blocks until some transaction against this guest becomes pending.
112/// For a non-blocking variant see [`try_recv_transaction_into`].
113///
114/// # Return value
115///
116/// - Returns `Err(n)` where `n` is the payload size if the buffer is too small.
117/// - On success returns `Ok((n, guest_id))` where `n` is the payload size and `guest_id` is the
118///   guest ID of the initiator.
119#[inline]
120pub fn recv_transaction_into(buf: &mut [u8]) -> Result<(usize, GuestId), usize> {
121	let ret = unsafe { c::recv_transaction(buf.as_mut_ptr() as u64, buf.len() as u64, 0) };
122	let (len, source) = RecvTransactionOutcome::from_reg(ret)
123		.into_inner()
124		.expect("The call is blocking");
125	if len > buf.len() as u32 {
126		return Err(len as usize);
127	}
128	Ok((len as usize, source))
129}
130
131/// Commits the transaction that was started by either [`initiate_transaction`] or
132/// [`recv_transaction`] or any of non-blocking variants of these calls.
133///
134/// Blocks until the other guest either calls [`commit_transaction`], or [`rollback_transaction`],
135/// or panics.
136///
137/// Panics if there is no active transaction.
138#[inline]
139pub fn commit_transaction() -> Result<(), TransactionFailed> {
140	unsafe { c::commit_transaction() }.into_result()
141}
142
143/// Cancels the transaction that was started by either [`initiate_transaction`] or
144/// [`recv_transaction`] or any of non-blocking variants of these calls.
145#[inline]
146pub fn rollback_transaction() {
147	unsafe { c::rollback_transaction() }
148}
149
150/// Ends the transaction that was started by either [`initiate_transaction`] or
151/// [`recv_transaction`] or any of non-blocking variants of these calls.
152#[inline]
153pub fn end_transaction() {
154	unsafe { c::end_transaction() }
155}
156
157#[derive(Debug, PartialEq, Eq)]
158enum TransactionState {
159	/// Transaction was initiated or received.
160	Initial,
161	/// Transaction was committed or rolled back.
162	CommittedOrRolledBack,
163	/// Transaction ended.
164	Ended,
165}
166
167/// Transaction wrapper that automatically rolls back and ends unfinished transaction once it goes
168/// out of scope.
169///
170/// # Example
171///
172/// Initiator:
173/// ```rust,ignore
174/// let mut tx = Transaction::initiate(123, &[]);
175/// // Do the changes here...
176/// if tx.commit().is_err() {
177///     // Rollback the changes here...
178/// }
179/// tx.end();
180/// ```
181///
182/// Processor:
183/// ```rust,ignore
184/// let (mut tx, payload, initiator_id) = Transaction::recv();
185/// // Do the changes here...
186/// if tx.commit().is_err() {
187///     // Rollback the changes here...
188/// }
189/// tx.end();
190/// ```
191pub struct Transaction {
192	state: TransactionState,
193}
194
195impl Transaction {
196	/// Initiates a transaction with `target` providing the `payload`.
197	///
198	/// Wraps [`initiate_transaction`].
199	#[inline]
200	pub fn initiate(target: GuestId, payload: &[u8]) -> Result<Self, TransactionFailed> {
201		initiate_transaction(target, payload)?;
202		Ok(Self { state: TransactionState::Initial })
203	}
204
205	/// Receives a transaction from the transaction initiator.
206	///
207	/// Returns new [`Transaction`] instance, the payload and the guest ID of the initiator.
208	///
209	/// Wraps [`recv_transaction`].
210	#[cfg(feature = "alloc")]
211	#[inline]
212	pub fn recv() -> Result<(Self, alloc::vec::Vec<u8>, GuestId), TransactionFailed> {
213		let (payload, source) = recv_transaction();
214		let tx = Self { state: TransactionState::Initial };
215		Ok((tx, payload, source))
216	}
217
218	/// Receives a transaction from the transaction initiator.
219	///
220	/// Copies the payload to `payload`.
221	///
222	/// Returns new [`Transaction`] instance, the size of the payload and the guest ID of the
223	/// initiator.
224	///
225	/// Wraps [`recv_transaction_into`].
226	#[inline]
227	pub fn recv_into(payload: &mut [u8]) -> Result<(Self, usize, GuestId), usize> {
228		let (payload_len, source) = recv_transaction_into(payload)?;
229		let tx = Self { state: TransactionState::Initial };
230		Ok((tx, payload_len, source))
231	}
232
233	/// Commits the transaction.
234	///
235	/// Panics if the transaction has already been committed or rolled back.
236	///
237	/// Wraps [`commit_transaction`].
238	#[inline]
239	pub fn commit(&mut self) -> Result<(), TransactionFailed> {
240		assert_eq!(TransactionState::Initial, self.state);
241		commit_transaction()?;
242		self.state = TransactionState::CommittedOrRolledBack;
243		Ok(())
244	}
245
246	/// Rolls back the transaction.
247	///
248	/// Panics if the transaction has already been committed or rolled back.
249	///
250	/// Wraps [`rollback_transaction`].
251	#[inline]
252	pub fn rollback(&mut self) {
253		assert_eq!(TransactionState::Initial, self.state);
254		rollback_transaction();
255		self.state = TransactionState::CommittedOrRolledBack;
256	}
257
258	/// Ends the transaction.
259	///
260	/// Panics if the transaction hasn't been committed or rolled back.
261	///
262	/// Wraps [`end_transaction`].
263	#[inline]
264	pub fn end(mut self) {
265		self.inner_end();
266	}
267
268	fn inner_end(&mut self) {
269		assert_eq!(TransactionState::CommittedOrRolledBack, self.state);
270		end_transaction();
271		self.state = TransactionState::Ended;
272	}
273}
274
275impl Drop for Transaction {
276	fn drop(&mut self) {
277		if self.state == TransactionState::Initial {
278			self.rollback();
279		}
280		if self.state == TransactionState::CommittedOrRolledBack {
281			self.inner_end();
282		}
283	}
284}