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