#![allow(clippy::missing_safety_doc)]
use crate::{c, flags::RecvTransaction, GuestId, RecvTransactionOutcome};
use core::mem::size_of_val;
#[derive(Debug)]
pub struct TransactionFailed;
impl core::fmt::Display for TransactionFailed {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("Transaction failed")
}
}
trait RetToResult {
fn into_result(self) -> Result<(), TransactionFailed>;
}
impl RetToResult for u64 {
fn into_result(self) -> Result<(), TransactionFailed> {
if self == 0 {
Ok(())
} else {
Err(TransactionFailed)
}
}
}
pub fn initiate_transaction(target: GuestId, payload: &[u8]) -> Result<(), TransactionFailed> {
let address = payload.as_ptr() as u64;
let len = size_of_val(payload) as u64;
unsafe { c::initiate_transaction(u64::from(target), address, len) }.into_result()
}
#[cfg(feature = "alloc")]
pub fn try_recv_transaction() -> Option<(alloc::vec::Vec<u8>, GuestId)> {
let ret = unsafe { c::recv_transaction(0, 0, RecvTransaction::NON_BLOCKING.bits()) };
let (len, source) = RecvTransactionOutcome::from_reg(ret).into_inner()?;
let mut buf = alloc::vec::Vec::with_capacity(len as usize);
unsafe {
c::recv_transaction(
buf.as_mut_ptr() as u64,
u64::from(len),
RecvTransaction::NON_BLOCKING.bits(),
)
};
unsafe { buf.set_len(len as usize) };
Some((buf, source))
}
#[cfg(feature = "alloc")]
pub fn recv_transaction() -> (alloc::vec::Vec<u8>, GuestId) {
let ret = unsafe { c::recv_transaction(0, 0, 0) };
let (len, source) = RecvTransactionOutcome::from_reg(ret)
.into_inner()
.expect("The call is blocking");
let mut buf = alloc::vec::Vec::with_capacity(len as usize);
unsafe { c::recv_transaction(buf.as_mut_ptr() as u64, u64::from(len), 0) };
unsafe { buf.set_len(len as usize) };
(buf, source)
}
#[inline]
pub fn try_recv_transaction_into(buf: &mut [u8]) -> Option<Result<(usize, GuestId), usize>> {
let ret = unsafe {
c::recv_transaction(
buf.as_mut_ptr() as u64,
buf.len() as u64,
RecvTransaction::NON_BLOCKING.bits(),
)
};
let (len, source) = RecvTransactionOutcome::from_reg(ret).into_inner()?;
if len > buf.len() as u32 {
return Some(Err(len as usize));
}
Some(Ok((len as usize, source)))
}
#[inline]
pub fn recv_transaction_into(buf: &mut [u8]) -> Result<(usize, GuestId), usize> {
let ret = unsafe { c::recv_transaction(buf.as_mut_ptr() as u64, buf.len() as u64, 0) };
let (len, source) = RecvTransactionOutcome::from_reg(ret)
.into_inner()
.expect("The call is blocking");
if len > buf.len() as u32 {
return Err(len as usize);
}
Ok((len as usize, source))
}
#[inline]
pub fn commit_transaction() -> Result<(), TransactionFailed> {
unsafe { c::commit_transaction() }.into_result()
}
#[inline]
pub fn rollback_transaction() {
unsafe { c::rollback_transaction() }
}
#[inline]
pub fn end_transaction() {
unsafe { c::end_transaction() }
}
#[derive(Debug, PartialEq, Eq)]
enum TransactionState {
Initial,
CommittedOrRolledBack,
Ended,
}
pub struct Transaction {
state: TransactionState,
}
impl Transaction {
#[inline]
pub fn initiate(target: GuestId, payload: &[u8]) -> Result<Self, TransactionFailed> {
initiate_transaction(target, payload)?;
Ok(Self { state: TransactionState::Initial })
}
#[cfg(feature = "alloc")]
#[inline]
pub fn recv() -> Result<(Self, alloc::vec::Vec<u8>, GuestId), TransactionFailed> {
let (payload, source) = recv_transaction();
let tx = Self { state: TransactionState::Initial };
Ok((tx, payload, source))
}
#[inline]
pub fn recv_into(payload: &mut [u8]) -> Result<(Self, usize, GuestId), usize> {
let (payload_len, source) = recv_transaction_into(payload)?;
let tx = Self { state: TransactionState::Initial };
Ok((tx, payload_len, source))
}
#[inline]
pub fn commit(&mut self) -> Result<(), TransactionFailed> {
assert_eq!(TransactionState::Initial, self.state);
commit_transaction()?;
self.state = TransactionState::CommittedOrRolledBack;
Ok(())
}
#[inline]
pub fn rollback(&mut self) {
assert_eq!(TransactionState::Initial, self.state);
rollback_transaction();
self.state = TransactionState::CommittedOrRolledBack;
}
#[inline]
pub fn end(mut self) {
self.inner_end();
}
fn inner_end(&mut self) {
assert_eq!(TransactionState::CommittedOrRolledBack, self.state);
end_transaction();
self.state = TransactionState::Ended;
}
}
impl Drop for Transaction {
fn drop(&mut self) {
if self.state == TransactionState::Initial {
self.rollback();
}
if self.state == TransactionState::CommittedOrRolledBack {
self.inner_end();
}
}
}