Skip to main content

selium_abi/
lib.rs

1//! Shared Application Binary Interface helpers for Selium host ↔ guest calls.
2
3use rkyv::{
4    Archive, Deserialize, Serialize,
5    api::high::{HighDeserializer, HighSerializer, HighValidator},
6    rancor::Error as RancorError,
7    ser::allocator::ArenaHandle,
8    util::AlignedVec,
9};
10use std::{
11    cmp::Ordering,
12    fmt::{Display, Formatter},
13};
14use thiserror::Error;
15
16pub mod hostcalls;
17mod io;
18mod net;
19mod process;
20mod session;
21mod singleton;
22mod time;
23mod tls;
24
25// pub use external::*;
26pub use hostcalls::*;
27pub use io::*;
28pub use net::*;
29pub use process::*;
30pub use session::*;
31pub use singleton::*;
32pub use time::*;
33pub use tls::*;
34
35/// Guest pointer-sized signed integer.
36pub type GuestInt = i32;
37/// Guest pointer-sized unsigned integer.
38pub type GuestUint = u32;
39/// Guest-facing resource identifiers.
40pub type GuestResourceId = u64;
41/// Guest pointer-sized atomic unsigned integer.
42pub type GuestAtomicUint = std::sync::atomic::AtomicU32;
43
44/// Size, in bytes, of a guest machine word.
45pub const WORD_SIZE: usize = 4;
46/// Marker bit used to differentiate driver poll results from payload lengths.
47const DRIVER_RESULT_SPECIAL_FLAG: GuestUint = 1 << 31;
48/// Maximum payload length representable in a driver poll result word.
49pub const DRIVER_RESULT_READY_MAX: GuestUint = DRIVER_RESULT_SPECIAL_FLAG - 1;
50/// Word signalling the host is still processing the driver future.
51pub const DRIVER_RESULT_PENDING: GuestUint = DRIVER_RESULT_SPECIAL_FLAG;
52/// Error code indicating the payload buffer contains a driver error string.
53pub const DRIVER_ERROR_MESSAGE_CODE: GuestUint = 1;
54
55/// Shared constants describing the guest↔host waker mailbox layout.
56pub mod mailbox {
57    use super::{GuestAtomicUint, GuestUint, WORD_SIZE};
58
59    /// Number of wake entries the ring buffer can hold.
60    pub const CAPACITY: GuestUint = 256;
61    /// Size in bytes of each ring entry.
62    pub const SLOT_SIZE: usize = core::mem::size_of::<GuestUint>();
63    /// Offset of the ready flag within the mailbox region.
64    pub const FLAG_OFFSET: usize = WORD_SIZE;
65    /// Offset of the head cursor within the mailbox region.
66    pub const HEAD_OFFSET: usize = WORD_SIZE * 2;
67    /// Offset of the tail cursor within the mailbox region.
68    pub const TAIL_OFFSET: usize = WORD_SIZE * 3;
69    /// Offset of the ring buffer within the mailbox region.
70    pub const RING_OFFSET: usize = WORD_SIZE * 4;
71
72    /// Atomic cell used for each mailbox slot.
73    pub type Cell = GuestAtomicUint;
74}
75
76/// Size in bytes of the guest mailbox region (head/tail cursors + wake ring).
77const MAILBOX_BYTES: usize =
78    mailbox::RING_OFFSET + (mailbox::CAPACITY as usize * mailbox::SLOT_SIZE);
79
80/// Default offset used by [`CallPlan`] when laying out transient buffers.
81///
82/// The mailbox occupies the first page of guest memory; buffers must start after it to avoid
83/// clobbering the wake ring.
84pub const DEFAULT_BUFFER_BASE: GuestUint = MAILBOX_BYTES as GuestUint;
85
86/// Trait for values that can be encoded using Selium's rkyv settings.
87pub trait RkyvEncode:
88    Archive + for<'a> Serialize<HighSerializer<AlignedVec, ArenaHandle<'a>, RancorError>>
89{
90}
91
92impl<T> RkyvEncode for T where
93    T: Archive + for<'a> Serialize<HighSerializer<AlignedVec, ArenaHandle<'a>, RancorError>>
94{
95}
96
97/// Decoded driver poll result.
98#[derive(Debug, Copy, Clone, PartialEq, Eq)]
99pub enum DriverPollResult {
100    /// Host completed the call and wrote `len` bytes into the result buffer.
101    Ready(GuestUint),
102    /// Host has not completed execution; guest should poll again later.
103    Pending,
104    /// Host reported an error; `code` identifies the error class.
105    Error(GuestUint),
106}
107
108/// Kernel capability identifiers shared between host and guest.
109#[repr(u8)]
110#[derive(
111    Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Archive, Serialize, Deserialize,
112)]
113#[rkyv(bytecheck())]
114pub enum Capability {
115    SessionLifecycle = 0,
116    ChannelLifecycle = 1,
117    ChannelReader = 2,
118    ChannelWriter = 3,
119    ProcessLifecycle = 4,
120    NetQuicBind = 5,
121    NetQuicAccept = 6,
122    NetQuicConnect = 7,
123    NetQuicRead = 8,
124    NetQuicWrite = 9,
125    NetHttpBind = 10,
126    NetHttpAccept = 11,
127    NetHttpConnect = 12,
128    NetHttpRead = 13,
129    NetHttpWrite = 14,
130    NetTlsServerConfig = 15,
131    NetTlsClientConfig = 16,
132    SingletonRegistry = 17,
133    SingletonLookup = 18,
134    TimeRead = 19,
135}
136
137impl Capability {
138    /// All capabilities understood by the Selium kernel ABI.
139    pub const ALL: [Capability; 20] = [
140        Capability::SessionLifecycle,
141        Capability::ChannelLifecycle,
142        Capability::ChannelReader,
143        Capability::ChannelWriter,
144        Capability::ProcessLifecycle,
145        Capability::NetQuicBind,
146        Capability::NetQuicAccept,
147        Capability::NetQuicConnect,
148        Capability::NetQuicRead,
149        Capability::NetQuicWrite,
150        Capability::NetHttpBind,
151        Capability::NetHttpAccept,
152        Capability::NetHttpConnect,
153        Capability::NetHttpRead,
154        Capability::NetHttpWrite,
155        Capability::NetTlsServerConfig,
156        Capability::NetTlsClientConfig,
157        Capability::SingletonRegistry,
158        Capability::SingletonLookup,
159        Capability::TimeRead,
160    ];
161}
162
163/// Error produced when decoding a capability identifier fails.
164#[derive(Debug, Error, Eq, PartialEq)]
165#[error("unknown capability identifier")]
166pub struct CapabilityDecodeError;
167
168/// Scalar value kinds supported by the ABI.
169#[derive(Debug, Clone, Copy, PartialEq, Archive, Serialize, Deserialize)]
170#[rkyv(bytecheck())]
171pub enum AbiScalarValue {
172    /// 8-bit signed integer.
173    I8(i8),
174    /// 8-bit unsigned integer.
175    U8(u8),
176    /// 16-bit signed integer.
177    I16(i16),
178    /// 16-bit unsigned integer.
179    U16(u16),
180    /// 32-bit signed integer.
181    I32(i32),
182    /// 32-bit unsigned integer.
183    U32(u32),
184    /// 64-bit signed integer.
185    I64(i64),
186    /// 64-bit unsigned integer.
187    U64(u64),
188    /// 32-bit IEEE float.
189    F32(f32),
190    /// 64-bit IEEE float.
191    F64(f64),
192}
193
194/// Scalar kinds that can be part of an ABI signature.
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Archive, Serialize, Deserialize)]
196#[rkyv(bytecheck())]
197pub enum AbiScalarType {
198    /// 8-bit signed integer.
199    I8,
200    /// 8-bit unsigned integer.
201    U8,
202    /// 16-bit signed integer.
203    I16,
204    /// 16-bit unsigned integer.
205    U16,
206    /// 32-bit signed integer.
207    I32,
208    /// 32-bit unsigned integer.
209    U32,
210    /// 64-bit signed integer.
211    I64,
212    /// 64-bit unsigned integer.
213    U64,
214    /// 32-bit IEEE float.
215    F32,
216    /// 64-bit IEEE float.
217    F64,
218}
219
220/// Logical parameter kinds supported by the ABI.
221#[derive(Debug, Clone, Copy, PartialEq, Eq, Archive, Serialize, Deserialize)]
222#[rkyv(bytecheck())]
223pub enum AbiParam {
224    /// An immediate scalar value.
225    Scalar(AbiScalarType),
226    /// A byte buffer passed via (ptr, len) pair.
227    Buffer,
228}
229
230/// Description of a guest entrypoint's parameters and results.
231#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)]
232#[rkyv(bytecheck())]
233pub struct AbiSignature {
234    params: Vec<AbiParam>,
235    results: Vec<AbiParam>,
236}
237
238/// Values supplied for a call.
239#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize)]
240#[rkyv(bytecheck())]
241pub enum AbiValue {
242    /// Scalar argument.
243    Scalar(AbiScalarValue),
244    /// Buffer argument (passed via pointer/length).
245    Buffer(Vec<u8>),
246}
247
248/// Planned argument + buffer layout suitable for host execution.
249#[derive(Debug, Clone)]
250pub struct CallPlan {
251    args: Vec<AbiScalarValue>,
252    writes: Vec<MemoryWrite>,
253    base_offset: GuestUint,
254}
255
256/// Host-side write required to materialise a buffer value.
257#[derive(Debug, Clone)]
258pub struct MemoryWrite {
259    /// Offset inside the guest linear memory.
260    pub offset: GuestUint,
261    /// Bytes that should be copied to guest memory.
262    pub bytes: Vec<u8>,
263}
264
265/// Errors raised when a call plan cannot be created.
266#[derive(Debug, Error)]
267pub enum CallPlanError {
268    /// Number of supplied values does not match the signature.
269    #[error("parameter count mismatch: expected {expected}, got {actual}")]
270    ParameterCount { expected: usize, actual: usize },
271    /// A value does not satisfy the parameter kind.
272    #[error("value mismatch at index {index}: {reason}")]
273    ValueMismatch { index: usize, reason: &'static str },
274    /// A buffer could not be laid out due to arithmetic overflow.
275    #[error("buffer layout overflowed guest address space")]
276    BufferOverflow,
277}
278
279/// Errors returned when serialising or deserialising rkyv payloads.
280#[derive(Debug, Error)]
281pub enum RkyvError {
282    /// Encoding the payload failed.
283    #[error("rkyv encode failed: {0}")]
284    Encode(String),
285    /// Decoding the payload failed.
286    #[error("rkyv decode failed: {0}")]
287    Decode(String),
288}
289
290impl Capability {
291    fn as_u8(self) -> u8 {
292        self as u8
293    }
294}
295
296impl TryFrom<u8> for Capability {
297    type Error = CapabilityDecodeError;
298
299    fn try_from(value: u8) -> Result<Self, Self::Error> {
300        match value {
301            0 => Ok(Capability::SessionLifecycle),
302            1 => Ok(Capability::ChannelLifecycle),
303            2 => Ok(Capability::ChannelReader),
304            3 => Ok(Capability::ChannelWriter),
305            4 => Ok(Capability::ProcessLifecycle),
306            5 => Ok(Capability::NetQuicBind),
307            6 => Ok(Capability::NetQuicAccept),
308            7 => Ok(Capability::NetQuicConnect),
309            8 => Ok(Capability::NetQuicRead),
310            9 => Ok(Capability::NetQuicWrite),
311            10 => Ok(Capability::NetHttpBind),
312            11 => Ok(Capability::NetHttpAccept),
313            12 => Ok(Capability::NetHttpConnect),
314            13 => Ok(Capability::NetHttpRead),
315            14 => Ok(Capability::NetHttpWrite),
316            15 => Ok(Capability::NetTlsServerConfig),
317            16 => Ok(Capability::NetTlsClientConfig),
318            17 => Ok(Capability::SingletonRegistry),
319            18 => Ok(Capability::SingletonLookup),
320            19 => Ok(Capability::TimeRead),
321            _ => Err(CapabilityDecodeError),
322        }
323    }
324}
325
326impl From<Capability> for u8 {
327    fn from(value: Capability) -> Self {
328        value.as_u8()
329    }
330}
331
332impl Display for Capability {
333    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
334        match self {
335            Capability::SessionLifecycle => write!(f, "SessionLifecycle"),
336            Capability::ChannelLifecycle => write!(f, "ChannelLifecycle"),
337            Capability::ChannelReader => write!(f, "ChannelReader"),
338            Capability::ChannelWriter => write!(f, "ChannelWriter"),
339            Capability::ProcessLifecycle => write!(f, "ProcessLifecycle"),
340            Capability::NetQuicBind => write!(f, "NetQuicBind"),
341            Capability::NetQuicAccept => write!(f, "NetQuicAccept"),
342            Capability::NetQuicConnect => write!(f, "NetQuicConnect"),
343            Capability::NetQuicRead => write!(f, "NetQuicRead"),
344            Capability::NetQuicWrite => write!(f, "NetQuicWrite"),
345            Capability::NetHttpBind => write!(f, "NetHttpBind"),
346            Capability::NetHttpAccept => write!(f, "NetHttpAccept"),
347            Capability::NetHttpConnect => write!(f, "NetHttpConnect"),
348            Capability::NetHttpRead => write!(f, "NetHttpRead"),
349            Capability::NetHttpWrite => write!(f, "NetHttpWrite"),
350            Capability::NetTlsClientConfig => write!(f, "NetTlsClientConfig"),
351            Capability::NetTlsServerConfig => write!(f, "NetTlsServerConfig"),
352            Capability::SingletonRegistry => write!(f, "SingletonRegistry"),
353            Capability::SingletonLookup => write!(f, "SingletonLookup"),
354            Capability::TimeRead => write!(f, "TimeRead"),
355        }
356    }
357}
358
359impl AbiScalarValue {
360    pub fn kind(&self) -> AbiScalarType {
361        match self {
362            AbiScalarValue::I8(_) => AbiScalarType::I8,
363            AbiScalarValue::U8(_) => AbiScalarType::U8,
364            AbiScalarValue::I16(_) => AbiScalarType::I16,
365            AbiScalarValue::U16(_) => AbiScalarType::U16,
366            AbiScalarValue::I32(_) => AbiScalarType::I32,
367            AbiScalarValue::U32(_) => AbiScalarType::U32,
368            AbiScalarValue::I64(_) => AbiScalarType::I64,
369            AbiScalarValue::U64(_) => AbiScalarType::U64,
370            AbiScalarValue::F32(_) => AbiScalarType::F32,
371            AbiScalarValue::F64(_) => AbiScalarType::F64,
372        }
373    }
374}
375
376impl AbiSignature {
377    pub fn new(params: Vec<AbiParam>, results: Vec<AbiParam>) -> Self {
378        Self { params, results }
379    }
380
381    pub fn params(&self) -> &[AbiParam] {
382        &self.params
383    }
384
385    pub fn results(&self) -> &[AbiParam] {
386        &self.results
387    }
388}
389
390impl From<i32> for AbiValue {
391    fn from(value: i32) -> Self {
392        Self::Scalar(AbiScalarValue::I32(value))
393    }
394}
395
396impl From<u16> for AbiValue {
397    fn from(value: u16) -> Self {
398        Self::Scalar(AbiScalarValue::U16(value))
399    }
400}
401
402impl From<i16> for AbiValue {
403    fn from(value: i16) -> Self {
404        Self::Scalar(AbiScalarValue::I16(value))
405    }
406}
407
408impl From<u8> for AbiValue {
409    fn from(value: u8) -> Self {
410        Self::Scalar(AbiScalarValue::U8(value))
411    }
412}
413
414impl From<i8> for AbiValue {
415    fn from(value: i8) -> Self {
416        Self::Scalar(AbiScalarValue::I8(value))
417    }
418}
419
420impl From<u32> for AbiValue {
421    fn from(value: u32) -> Self {
422        Self::Scalar(AbiScalarValue::U32(value))
423    }
424}
425
426impl From<i64> for AbiValue {
427    fn from(value: i64) -> Self {
428        Self::Scalar(AbiScalarValue::I64(value))
429    }
430}
431
432impl From<u64> for AbiValue {
433    fn from(value: u64) -> Self {
434        Self::Scalar(AbiScalarValue::U64(value))
435    }
436}
437
438impl From<f32> for AbiValue {
439    fn from(value: f32) -> Self {
440        Self::Scalar(AbiScalarValue::F32(value))
441    }
442}
443
444impl From<f64> for AbiValue {
445    fn from(value: f64) -> Self {
446        Self::Scalar(AbiScalarValue::F64(value))
447    }
448}
449
450impl From<Vec<u8>> for AbiValue {
451    fn from(value: Vec<u8>) -> Self {
452        Self::Buffer(value)
453    }
454}
455
456impl CallPlan {
457    pub fn new(signature: &AbiSignature, values: &[AbiValue]) -> Result<Self, CallPlanError> {
458        Self::with_base(signature, values, DEFAULT_BUFFER_BASE)
459    }
460
461    pub fn with_base(
462        signature: &AbiSignature,
463        values: &[AbiValue],
464        base_offset: GuestUint,
465    ) -> Result<Self, CallPlanError> {
466        if signature.params.len() != values.len() {
467            return Err(CallPlanError::ParameterCount {
468                expected: signature.params.len(),
469                actual: values.len(),
470            });
471        }
472
473        let mut args = Vec::new();
474        let mut writes = Vec::new();
475        let mut cursor = base_offset;
476
477        for (index, (param, value)) in signature.params.iter().zip(values).enumerate() {
478            match (param, value) {
479                (AbiParam::Scalar(expected), AbiValue::Scalar(actual)) => {
480                    if actual.kind() != *expected {
481                        return Err(CallPlanError::ValueMismatch {
482                            index,
483                            reason: "scalar type mismatch",
484                        });
485                    }
486                    append_scalar_args(&mut args, *expected, *actual, index)?;
487                }
488                (AbiParam::Buffer, AbiValue::Buffer(bytes)) => {
489                    let len_u32 =
490                        u32::try_from(bytes.len()).map_err(|_| CallPlanError::BufferOverflow)?;
491                    if len_u32 == 0 {
492                        args.push(AbiScalarValue::I32(0));
493                        args.push(AbiScalarValue::I32(0));
494                        continue;
495                    }
496
497                    let ptr = cursor;
498                    cursor = align_offset(cursor, bytes.len())?;
499
500                    args.push(AbiScalarValue::I32(i32::try_from(ptr).map_err(|_| {
501                        CallPlanError::ValueMismatch {
502                            index,
503                            reason: "pointer does not fit i32",
504                        }
505                    })?));
506                    args.push(AbiScalarValue::I32(i32::try_from(len_u32).map_err(
507                        |_| CallPlanError::ValueMismatch {
508                            index,
509                            reason: "length does not fit i32",
510                        },
511                    )?));
512                    writes.push(MemoryWrite {
513                        offset: ptr,
514                        bytes: bytes.clone(),
515                    });
516                }
517                (AbiParam::Scalar(_), AbiValue::Buffer(_)) => {
518                    return Err(CallPlanError::ValueMismatch {
519                        index,
520                        reason: "expected scalar, found buffer",
521                    });
522                }
523                (AbiParam::Buffer, AbiValue::Scalar(_)) => {
524                    return Err(CallPlanError::ValueMismatch {
525                        index,
526                        reason: "expected buffer, found scalar",
527                    });
528                }
529            }
530        }
531
532        Ok(Self {
533            args,
534            writes,
535            base_offset,
536        })
537    }
538
539    pub fn params(&self) -> &[AbiScalarValue] {
540        &self.args
541    }
542
543    pub fn memory_writes(&self) -> &[MemoryWrite] {
544        &self.writes
545    }
546
547    pub fn base_offset(&self) -> GuestUint {
548        self.base_offset
549    }
550}
551
552impl From<DriverPollResult> for GuestUint {
553    fn from(value: DriverPollResult) -> Self {
554        match value {
555            DriverPollResult::Ready(len) => len,
556            DriverPollResult::Pending => DRIVER_RESULT_PENDING,
557            DriverPollResult::Error(code) => driver_encode_error(code),
558        }
559    }
560}
561
562impl TryFrom<GuestUint> for DriverPollResult {
563    type Error = CapabilityDecodeError;
564
565    fn try_from(word: GuestUint) -> Result<Self, <Self as TryFrom<GuestUint>>::Error> {
566        Ok(driver_decode_result(word))
567    }
568}
569
570/// Encode a value into rkyv bytes using Selium's settings.
571pub fn encode_rkyv<T>(value: &T) -> Result<Vec<u8>, RkyvError>
572where
573    T: RkyvEncode,
574{
575    rkyv::to_bytes::<RancorError>(value)
576        .map(|bytes| bytes.into_vec())
577        .map_err(|err| RkyvError::Encode(err.to_string()))
578}
579
580/// Decode a value from rkyv bytes using Selium's settings.
581pub fn decode_rkyv<T>(bytes: &[u8]) -> Result<T, RkyvError>
582where
583    T: Archive + Sized,
584    for<'a> T::Archived: 'a
585        + Deserialize<T, HighDeserializer<RancorError>>
586        + rkyv::bytecheck::CheckBytes<HighValidator<'a, RancorError>>,
587{
588    rkyv::from_bytes::<T, RancorError>(bytes).map_err(|err| RkyvError::Decode(err.to_string()))
589}
590
591/// Encode a human-readable driver error message for guest consumption.
592pub fn encode_driver_error_message(message: &str) -> Result<Vec<u8>, RkyvError> {
593    let encoded = encode_rkyv(&message.to_string())?;
594    let len = u32::try_from(encoded.len()).map_err(|_| {
595        RkyvError::Encode("driver error message length does not fit u32".to_string())
596    })?;
597    let mut bytes = Vec::with_capacity(encoded.len() + 4);
598    bytes.extend_from_slice(&len.to_le_bytes());
599    bytes.extend_from_slice(&encoded);
600    Ok(bytes)
601}
602
603/// Decode a driver error message payload written by the kernel.
604pub fn decode_driver_error_message(bytes: &[u8]) -> Result<String, RkyvError> {
605    let prefix = bytes
606        .get(..4)
607        .ok_or_else(|| RkyvError::Decode("driver error message missing length".to_string()))?;
608    let len = u32::from_le_bytes(
609        prefix
610            .try_into()
611            .map_err(|_| RkyvError::Decode("driver error message length malformed".to_string()))?,
612    ) as usize;
613    let payload = bytes.get(4..4 + len).ok_or_else(|| {
614        RkyvError::Decode("driver error message length exceeds buffer".to_string())
615    })?;
616    decode_rkyv::<String>(payload)
617}
618
619pub fn driver_encode_ready(len: GuestUint) -> Option<GuestUint> {
620    if len > DRIVER_RESULT_READY_MAX {
621        None
622    } else {
623        Some(len)
624    }
625}
626
627pub fn driver_encode_error(mut code: GuestUint) -> GuestUint {
628    if code == 0 {
629        code = DRIVER_ERROR_MESSAGE_CODE;
630    }
631    DRIVER_RESULT_SPECIAL_FLAG | (code & DRIVER_RESULT_READY_MAX)
632}
633
634pub fn driver_decode_result(word: GuestUint) -> DriverPollResult {
635    if word < DRIVER_RESULT_SPECIAL_FLAG {
636        DriverPollResult::Ready(word)
637    } else if word == DRIVER_RESULT_SPECIAL_FLAG {
638        DriverPollResult::Pending
639    } else {
640        DriverPollResult::Error(word & DRIVER_RESULT_READY_MAX)
641    }
642}
643
644fn append_scalar_args(
645    args: &mut Vec<AbiScalarValue>,
646    expected: AbiScalarType,
647    value: AbiScalarValue,
648    index: usize,
649) -> Result<(), CallPlanError> {
650    match (expected, value) {
651        (AbiScalarType::I8, AbiScalarValue::I8(v)) => {
652            args.push(AbiScalarValue::I32(i32::from(v)));
653        }
654        (AbiScalarType::U8, AbiScalarValue::U8(v)) => {
655            args.push(AbiScalarValue::I32(i32::from(v)));
656        }
657        (AbiScalarType::I16, AbiScalarValue::I16(v)) => {
658            args.push(AbiScalarValue::I32(i32::from(v)))
659        }
660        (AbiScalarType::U16, AbiScalarValue::U16(v)) => {
661            args.push(AbiScalarValue::I32(i32::from(v)))
662        }
663        (AbiScalarType::I32, AbiScalarValue::I32(v)) => args.push(AbiScalarValue::I32(v)),
664        (AbiScalarType::U32, AbiScalarValue::U32(v)) => {
665            args.push(AbiScalarValue::I32(i32::from_ne_bytes(v.to_ne_bytes())))
666        }
667        (AbiScalarType::F32, AbiScalarValue::F32(v)) => args.push(AbiScalarValue::F32(v)),
668        (AbiScalarType::F64, AbiScalarValue::F64(v)) => args.push(AbiScalarValue::F64(v)),
669        (AbiScalarType::I64, AbiScalarValue::I64(v)) => {
670            let (lo, hi) = split_i64(v);
671            args.push(AbiScalarValue::I32(lo));
672            args.push(AbiScalarValue::I32(hi));
673        }
674        (AbiScalarType::U64, AbiScalarValue::U64(v)) => {
675            let (lo, hi) = split_u64(v);
676            args.push(AbiScalarValue::I32(lo));
677            args.push(AbiScalarValue::I32(hi));
678        }
679        _ => {
680            return Err(CallPlanError::ValueMismatch {
681                index,
682                reason: "scalar type mismatch",
683            });
684        }
685    }
686
687    Ok(())
688}
689
690fn split_i64(value: i64) -> (i32, i32) {
691    let bytes = value.to_le_bytes();
692    let lo = i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
693    let hi = i32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
694    (lo, hi)
695}
696
697fn split_u64(value: u64) -> (i32, i32) {
698    let bytes = value.to_le_bytes();
699    let lo = i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
700    let hi = i32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
701    (lo, hi)
702}
703
704fn align_offset(current: GuestUint, len_bytes: usize) -> Result<GuestUint, CallPlanError> {
705    let len = GuestUint::try_from(len_bytes).map_err(|_| CallPlanError::BufferOverflow)?;
706    let align = GuestUint::try_from(WORD_SIZE).expect("word size fits into GuestUint");
707    let rounded = match len.cmp(&GuestUint::from(0u8)) {
708        Ordering::Equal => GuestUint::from(0u8),
709        _ => {
710            let remainder = len % align;
711            if remainder == GuestUint::from(0u8) {
712                len
713            } else {
714                len.checked_add(align - remainder)
715                    .ok_or(CallPlanError::BufferOverflow)?
716            }
717        }
718    };
719    current
720        .checked_add(rounded)
721        .ok_or(CallPlanError::BufferOverflow)
722}
723
724#[cfg(test)]
725mod tests {
726    use super::*;
727
728    #[test]
729    fn default_buffer_base_leaves_mailbox_intact() {
730        let mailbox_end =
731            (mailbox::RING_OFFSET + (mailbox::CAPACITY as usize * mailbox::SLOT_SIZE)) as GuestUint;
732        assert!(
733            DEFAULT_BUFFER_BASE >= mailbox_end,
734            "default buffer base {DEFAULT_BUFFER_BASE} overlaps mailbox (ends at {mailbox_end})"
735        );
736    }
737
738    #[test]
739    fn call_plan_flattens_integer_widths() {
740        let signature = AbiSignature::new(
741            vec![
742                AbiParam::Scalar(AbiScalarType::U16),
743                AbiParam::Scalar(AbiScalarType::U64),
744            ],
745            Vec::new(),
746        );
747        let values = vec![
748            AbiValue::Scalar(AbiScalarValue::U16(7)),
749            AbiValue::Scalar(AbiScalarValue::U64(0x0102_0304_0506_0708)),
750        ];
751
752        let plan = CallPlan::new(&signature, &values).expect("call plan creation should succeed");
753        let params = plan.params();
754
755        assert_eq!(
756            params.len(),
757            3,
758            "u16 should flatten to one word and u64 to two"
759        );
760
761        let first = params[0];
762        assert_eq!(first, AbiScalarValue::I32(7));
763
764        let lo_word = match params[1] {
765            AbiScalarValue::I32(v) => u32::from_ne_bytes(v.to_ne_bytes()),
766            other => panic!("expected low u64 word, found {other:?}"),
767        };
768        let hi_word = match params[2] {
769            AbiScalarValue::I32(v) => u32::from_ne_bytes(v.to_ne_bytes()),
770            other => panic!("expected high u64 word, found {other:?}"),
771        };
772
773        let combined = (u64::from(hi_word) << 32) | u64::from(lo_word);
774        assert_eq!(combined, 0x0102_0304_0506_0708);
775    }
776}