1use 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
25pub use hostcalls::*;
27pub use io::*;
28pub use net::*;
29pub use process::*;
30pub use session::*;
31pub use singleton::*;
32pub use time::*;
33pub use tls::*;
34
35pub type GuestInt = i32;
37pub type GuestUint = u32;
39pub type GuestResourceId = u64;
41pub type GuestAtomicUint = std::sync::atomic::AtomicU32;
43
44pub const WORD_SIZE: usize = 4;
46const DRIVER_RESULT_SPECIAL_FLAG: GuestUint = 1 << 31;
48pub const DRIVER_RESULT_READY_MAX: GuestUint = DRIVER_RESULT_SPECIAL_FLAG - 1;
50pub const DRIVER_RESULT_PENDING: GuestUint = DRIVER_RESULT_SPECIAL_FLAG;
52pub const DRIVER_ERROR_MESSAGE_CODE: GuestUint = 1;
54
55pub mod mailbox {
57 use super::{GuestAtomicUint, GuestUint, WORD_SIZE};
58
59 pub const CAPACITY: GuestUint = 256;
61 pub const SLOT_SIZE: usize = core::mem::size_of::<GuestUint>();
63 pub const FLAG_OFFSET: usize = WORD_SIZE;
65 pub const HEAD_OFFSET: usize = WORD_SIZE * 2;
67 pub const TAIL_OFFSET: usize = WORD_SIZE * 3;
69 pub const RING_OFFSET: usize = WORD_SIZE * 4;
71
72 pub type Cell = GuestAtomicUint;
74}
75
76const MAILBOX_BYTES: usize =
78 mailbox::RING_OFFSET + (mailbox::CAPACITY as usize * mailbox::SLOT_SIZE);
79
80pub const DEFAULT_BUFFER_BASE: GuestUint = MAILBOX_BYTES as GuestUint;
85
86pub 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#[derive(Debug, Copy, Clone, PartialEq, Eq)]
99pub enum DriverPollResult {
100 Ready(GuestUint),
102 Pending,
104 Error(GuestUint),
106}
107
108#[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 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#[derive(Debug, Error, Eq, PartialEq)]
165#[error("unknown capability identifier")]
166pub struct CapabilityDecodeError;
167
168#[derive(Debug, Clone, Copy, PartialEq, Archive, Serialize, Deserialize)]
170#[rkyv(bytecheck())]
171pub enum AbiScalarValue {
172 I8(i8),
174 U8(u8),
176 I16(i16),
178 U16(u16),
180 I32(i32),
182 U32(u32),
184 I64(i64),
186 U64(u64),
188 F32(f32),
190 F64(f64),
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Archive, Serialize, Deserialize)]
196#[rkyv(bytecheck())]
197pub enum AbiScalarType {
198 I8,
200 U8,
202 I16,
204 U16,
206 I32,
208 U32,
210 I64,
212 U64,
214 F32,
216 F64,
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq, Archive, Serialize, Deserialize)]
222#[rkyv(bytecheck())]
223pub enum AbiParam {
224 Scalar(AbiScalarType),
226 Buffer,
228}
229
230#[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#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize)]
240#[rkyv(bytecheck())]
241pub enum AbiValue {
242 Scalar(AbiScalarValue),
244 Buffer(Vec<u8>),
246}
247
248#[derive(Debug, Clone)]
250pub struct CallPlan {
251 args: Vec<AbiScalarValue>,
252 writes: Vec<MemoryWrite>,
253 base_offset: GuestUint,
254}
255
256#[derive(Debug, Clone)]
258pub struct MemoryWrite {
259 pub offset: GuestUint,
261 pub bytes: Vec<u8>,
263}
264
265#[derive(Debug, Error)]
267pub enum CallPlanError {
268 #[error("parameter count mismatch: expected {expected}, got {actual}")]
270 ParameterCount { expected: usize, actual: usize },
271 #[error("value mismatch at index {index}: {reason}")]
273 ValueMismatch { index: usize, reason: &'static str },
274 #[error("buffer layout overflowed guest address space")]
276 BufferOverflow,
277}
278
279#[derive(Debug, Error)]
281pub enum RkyvError {
282 #[error("rkyv encode failed: {0}")]
284 Encode(String),
285 #[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
570pub 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
580pub 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
591pub 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
603pub 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}