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 tls;
23
24pub use hostcalls::*;
26pub use io::*;
27pub use net::*;
28pub use process::*;
29pub use session::*;
30pub use singleton::*;
31pub use tls::*;
32
33pub type GuestInt = i32;
35pub type GuestUint = u32;
37pub type GuestResourceId = u64;
39pub type GuestAtomicUint = std::sync::atomic::AtomicU32;
41
42pub const WORD_SIZE: usize = 4;
44const DRIVER_RESULT_SPECIAL_FLAG: GuestUint = 1 << 31;
46pub const DRIVER_RESULT_READY_MAX: GuestUint = DRIVER_RESULT_SPECIAL_FLAG - 1;
48pub const DRIVER_RESULT_PENDING: GuestUint = DRIVER_RESULT_SPECIAL_FLAG;
50pub const DRIVER_ERROR_MESSAGE_CODE: GuestUint = 1;
52
53pub mod mailbox {
55 use super::{GuestAtomicUint, GuestUint, WORD_SIZE};
56
57 pub const CAPACITY: GuestUint = 256;
59 pub const SLOT_SIZE: usize = core::mem::size_of::<GuestUint>();
61 pub const FLAG_OFFSET: usize = WORD_SIZE;
63 pub const HEAD_OFFSET: usize = WORD_SIZE * 2;
65 pub const TAIL_OFFSET: usize = WORD_SIZE * 3;
67 pub const RING_OFFSET: usize = WORD_SIZE * 4;
69
70 pub type Cell = GuestAtomicUint;
72}
73
74const MAILBOX_BYTES: usize =
76 mailbox::RING_OFFSET + (mailbox::CAPACITY as usize * mailbox::SLOT_SIZE);
77
78pub const DEFAULT_BUFFER_BASE: GuestUint = MAILBOX_BYTES as GuestUint;
83
84pub 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#[derive(Debug, Copy, Clone, PartialEq, Eq)]
97pub enum DriverPollResult {
98 Ready(GuestUint),
100 Pending,
102 Error(GuestUint),
104}
105
106#[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 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#[derive(Debug, Error, Eq, PartialEq)]
161#[error("unknown capability identifier")]
162pub struct CapabilityDecodeError;
163
164#[derive(Debug, Clone, Copy, PartialEq, Archive, Serialize, Deserialize)]
166#[rkyv(bytecheck())]
167pub enum AbiScalarValue {
168 I8(i8),
170 U8(u8),
172 I16(i16),
174 U16(u16),
176 I32(i32),
178 U32(u32),
180 I64(i64),
182 U64(u64),
184 F32(f32),
186 F64(f64),
188}
189
190#[derive(Debug, Clone, Copy, PartialEq, Eq, Archive, Serialize, Deserialize)]
192#[rkyv(bytecheck())]
193pub enum AbiScalarType {
194 I8,
196 U8,
198 I16,
200 U16,
202 I32,
204 U32,
206 I64,
208 U64,
210 F32,
212 F64,
214}
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq, Archive, Serialize, Deserialize)]
218#[rkyv(bytecheck())]
219pub enum AbiParam {
220 Scalar(AbiScalarType),
222 Buffer,
224}
225
226#[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#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize)]
236#[rkyv(bytecheck())]
237pub enum AbiValue {
238 Scalar(AbiScalarValue),
240 Buffer(Vec<u8>),
242}
243
244#[derive(Debug, Clone)]
246pub struct CallPlan {
247 args: Vec<AbiScalarValue>,
248 writes: Vec<MemoryWrite>,
249 base_offset: GuestUint,
250}
251
252#[derive(Debug, Clone)]
254pub struct MemoryWrite {
255 pub offset: GuestUint,
257 pub bytes: Vec<u8>,
259}
260
261#[derive(Debug, Error)]
263pub enum CallPlanError {
264 #[error("parameter count mismatch: expected {expected}, got {actual}")]
266 ParameterCount { expected: usize, actual: usize },
267 #[error("value mismatch at index {index}: {reason}")]
269 ValueMismatch { index: usize, reason: &'static str },
270 #[error("buffer layout overflowed guest address space")]
272 BufferOverflow,
273}
274
275#[derive(Debug, Error)]
277pub enum RkyvError {
278 #[error("rkyv encode failed: {0}")]
280 Encode(String),
281 #[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
564pub 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
574pub 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
585pub 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
597pub 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}