diff --git a/CHANGELOG.md b/CHANGELOG.md index 12c6f13e2f..7024519ca9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). identical and its digest is unchanged, so this is transparent to readers and to previously saved snapshots. Filesystems that do not support sparse files store the blob as before. +* Expose C guest `ByteChunks` values as pointer and length arrays. +* Return typed `hl_ReturnValue` objects from C guest functions through + `hl_result_from_*` constructors. ### Removed diff --git a/src/hyperlight_common/src/flatbuffer_wrappers/codec.rs b/src/hyperlight_common/src/flatbuffer_wrappers/codec.rs new file mode 100644 index 0000000000..90919ea29e --- /dev/null +++ b/src/hyperlight_common/src/flatbuffer_wrappers/codec.rs @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2026 The Hyperlight Authors. + +use alloc::vec::Vec; + +use anyhow::Result; +use bytes::Bytes; + +/// Receives external byte values while their FlatBuffer metadata is encoded. +/// +/// Values are delivered in their logical order without flattening chunked +/// values. The value lifetime allows a sink to retain references until the +/// control buffer has been written, without copying payload bytes. +pub trait ExternalValueSink<'a> { + /// Receive one contiguous byte value. + fn push_bytes(&mut self, value: &'a [u8]) -> Result<()>; + + /// Receive one logical byte sequence represented as chunks. + fn push_chunks(&mut self, value: &'a [Bytes]) -> Result<()>; +} + +/// Supplies complete external byte values while a FlatBuffer is decoded. +/// +/// Implementations must validate `length` against available input and the +/// resource budget before allocating. +pub trait ExternalValueSource { + /// Take the next external value as contiguous bytes. + fn take_bytes(&mut self, length: usize) -> Result>; + + /// Take the next external value as owned byte chunks. + fn take_chunks(&mut self, length: usize) -> Result>; + + /// Finish decoding and reject any unused external values. + fn finish(&mut self) -> Result<()>; +} diff --git a/src/hyperlight_common/src/flatbuffer_wrappers/function_call.rs b/src/hyperlight_common/src/flatbuffer_wrappers/function_call.rs index 040b6e9a00..6cb272bc58 100644 --- a/src/hyperlight_common/src/flatbuffer_wrappers/function_call.rs +++ b/src/hyperlight_common/src/flatbuffer_wrappers/function_call.rs @@ -9,13 +9,16 @@ use flatbuffers::{FlatBufferBuilder, WIPOffset, size_prefixed_root}; #[cfg(feature = "tracing")] use tracing::{Span, instrument}; -use super::function_types::{ParameterValue, ReturnType}; +use super::codec::{ExternalValueSink, ExternalValueSource}; +use super::function_types::{ParameterValue, ReturnType, decode_external_parameter_value}; +use super::util::{byte_chunks_to_bytes, try_byte_chunks_len}; use crate::flatbuffers::hyperlight::generated::{ FunctionCall as FbFunctionCall, FunctionCallArgs as FbFunctionCallArgs, FunctionCallType as FbFunctionCallType, Parameter, ParameterArgs, - ParameterValue as FbParameterValue, hlbool, hlboolArgs, hldouble, hldoubleArgs, hlfloat, - hlfloatArgs, hlint, hlintArgs, hllong, hllongArgs, hlstring, hlstringArgs, hluint, hluintArgs, - hlulong, hlulongArgs, hlvecbytes, hlvecbytesArgs, + ParameterValue as FbParameterValue, hlbool, hlboolArgs, hlbytechunks, hlbytechunksArgs, + hldouble, hldoubleArgs, hlexternalbytes, hlexternalbytesArgs, hlfloat, hlfloatArgs, hlint, + hlintArgs, hllong, hllongArgs, hlstring, hlstringArgs, hluint, hluintArgs, hlulong, + hlulongArgs, hlvecbytes, hlvecbytesArgs, }; /// The type of function call. @@ -180,6 +183,23 @@ impl FunctionCall { }, ) } + ParameterValue::ByteChunks(v) => { + let value = byte_chunks_to_bytes(v); + let vec_bytes = builder.create_vector(value.as_ref()); + let hlbytechunks = hlbytechunks::create( + builder, + &hlbytechunksArgs { + value: Some(vec_bytes), + }, + ); + Parameter::create( + builder, + &ParameterArgs { + value_type: FbParameterValue::hlbytechunks, + value: Some(hlbytechunks.as_union_value()), + }, + ) + } }) .collect(); Some(builder.create_vector(¶meter_offsets)) @@ -199,6 +219,222 @@ impl FunctionCall { builder.finish_size_prefixed(function_call, None); builder.finished_data() } + + /// Encodes byte parameters as external markers and sends their payloads to + /// `external_values` in parameter order. + pub fn encode_external<'a, 'b, S>( + &'a self, + builder: &'b mut FlatBufferBuilder, + external_values: &mut S, + ) -> Result<&'b [u8]> + where + S: ExternalValueSink<'a> + ?Sized, + { + let function_name = builder.create_string(&self.function_name); + + let function_call_type = match self.function_call_type { + FunctionCallType::Guest => FbFunctionCallType::guest, + FunctionCallType::Host => FbFunctionCallType::host, + }; + + let expected_return_type = self.expected_return_type.into(); + + let parameters = match &self.parameters { + Some(parameters) if !parameters.is_empty() => { + let parameter_offsets: Vec> = parameters + .iter() + .map(|parameter| -> Result> { + let parameter = match parameter { + ParameterValue::Int(value) => { + let value = hlint::create(builder, &hlintArgs { value: *value }); + Parameter::create( + builder, + &ParameterArgs { + value_type: FbParameterValue::hlint, + value: Some(value.as_union_value()), + }, + ) + } + ParameterValue::UInt(value) => { + let value = hluint::create(builder, &hluintArgs { value: *value }); + Parameter::create( + builder, + &ParameterArgs { + value_type: FbParameterValue::hluint, + value: Some(value.as_union_value()), + }, + ) + } + ParameterValue::Long(value) => { + let value = hllong::create(builder, &hllongArgs { value: *value }); + Parameter::create( + builder, + &ParameterArgs { + value_type: FbParameterValue::hllong, + value: Some(value.as_union_value()), + }, + ) + } + ParameterValue::ULong(value) => { + let value = + hlulong::create(builder, &hlulongArgs { value: *value }); + Parameter::create( + builder, + &ParameterArgs { + value_type: FbParameterValue::hlulong, + value: Some(value.as_union_value()), + }, + ) + } + ParameterValue::Float(value) => { + let value = + hlfloat::create(builder, &hlfloatArgs { value: *value }); + Parameter::create( + builder, + &ParameterArgs { + value_type: FbParameterValue::hlfloat, + value: Some(value.as_union_value()), + }, + ) + } + ParameterValue::Double(value) => { + let value = + hldouble::create(builder, &hldoubleArgs { value: *value }); + Parameter::create( + builder, + &ParameterArgs { + value_type: FbParameterValue::hldouble, + value: Some(value.as_union_value()), + }, + ) + } + ParameterValue::Bool(value) => { + let value = hlbool::create(builder, &hlboolArgs { value: *value }); + Parameter::create( + builder, + &ParameterArgs { + value_type: FbParameterValue::hlbool, + value: Some(value.as_union_value()), + }, + ) + } + ParameterValue::String(value) => { + let value = builder.create_string(value.as_str()); + let value = + hlstring::create(builder, &hlstringArgs { value: Some(value) }); + Parameter::create( + builder, + &ParameterArgs { + value_type: FbParameterValue::hlstring, + value: Some(value.as_union_value()), + }, + ) + } + ParameterValue::VecBytes(value) => { + let length = u64::try_from(value.len()).map_err(|_| { + anyhow::anyhow!( + "External VecBytes parameter length does not fit in u64" + ) + })?; + external_values.push_bytes(value)?; + let value = hlexternalbytes::create( + builder, + &hlexternalbytesArgs { + length, + chunked: false, + }, + ); + Parameter::create( + builder, + &ParameterArgs { + value_type: FbParameterValue::hlexternalbytes, + value: Some(value.as_union_value()), + }, + ) + } + ParameterValue::ByteChunks(value) => { + let length = try_byte_chunks_len(value).ok_or_else(|| { + anyhow::anyhow!("External ByteChunks parameter length overflow") + })?; + let length = u64::try_from(length).map_err(|_| { + anyhow::anyhow!( + "External ByteChunks parameter length does not fit in u64" + ) + })?; + external_values.push_chunks(value)?; + let value = hlexternalbytes::create( + builder, + &hlexternalbytesArgs { + length, + chunked: true, + }, + ); + Parameter::create( + builder, + &ParameterArgs { + value_type: FbParameterValue::hlexternalbytes, + value: Some(value.as_union_value()), + }, + ) + } + }; + Ok(parameter) + }) + .collect::>>()?; + Some(builder.create_vector(¶meter_offsets)) + } + _ => None, + }; + + let function_call = FbFunctionCall::create( + builder, + &FbFunctionCallArgs { + function_name: Some(function_name), + parameters, + function_call_type, + expected_return_type, + }, + ); + builder.finish_size_prefixed(function_call, None); + Ok(builder.finished_data()) + } + + /// Decodes a function call using `external_values` for external byte + /// markers. + pub fn decode_external(value: &[u8], external_values: &mut S) -> Result + where + S: ExternalValueSource + ?Sized, + { + let function_call_fb = size_prefixed_root::(value) + .map_err(|e| anyhow::anyhow!("Error reading function call buffer: {:?}", e))?; + let function_name = function_call_fb.function_name(); + let function_call_type = match function_call_fb.function_call_type() { + FbFunctionCallType::guest => FunctionCallType::Guest, + FbFunctionCallType::host => FunctionCallType::Host, + other => { + bail!("Invalid function call type: {:?}", other); + } + }; + let expected_return_type = function_call_fb.expected_return_type().try_into()?; + + let parameters = function_call_fb + .parameters() + .map(|parameters| { + parameters + .iter() + .map(|parameter| decode_external_parameter_value(parameter, external_values)) + .collect::>>() + }) + .transpose()?; + + external_values.finish()?; + Ok(Self { + function_name: function_name.to_string(), + parameters, + function_call_type, + expected_return_type, + }) + } } #[cfg_attr(feature = "tracing", instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace"))] @@ -261,10 +497,74 @@ impl TryFrom<&[u8]> for FunctionCall { #[cfg(test)] mod tests { + use alloc::collections::VecDeque; use alloc::vec; use super::*; - use crate::flatbuffer_wrappers::function_types::ReturnType; + use crate::flatbuffer_wrappers::function_types::{Bytes, ReturnType}; + + #[derive(Debug, Clone, PartialEq)] + enum TestExternalValue { + VecBytes(Vec), + ByteChunks(Vec), + } + + #[derive(Default)] + struct TestExternalValues { + values: VecDeque, + } + + impl TestExternalValues { + fn from_values(values: impl IntoIterator) -> Self { + Self { + values: values.into_iter().collect(), + } + } + } + + impl<'a> ExternalValueSink<'a> for TestExternalValues { + fn push_bytes(&mut self, value: &'a [u8]) -> Result<()> { + self.values + .push_back(TestExternalValue::VecBytes(value.to_vec())); + Ok(()) + } + + fn push_chunks(&mut self, value: &'a [Bytes]) -> Result<()> { + self.values + .push_back(TestExternalValue::ByteChunks(value.to_vec())); + Ok(()) + } + } + + impl ExternalValueSource for TestExternalValues { + fn take_bytes(&mut self, _length: usize) -> Result> { + match self.values.pop_front() { + Some(TestExternalValue::VecBytes(value)) => Ok(value), + Some(TestExternalValue::ByteChunks(_)) => { + anyhow::bail!("Expected external VecBytes value") + } + None => anyhow::bail!("Missing external VecBytes value"), + } + } + + fn take_chunks(&mut self, _length: usize) -> Result> { + match self.values.pop_front() { + Some(TestExternalValue::ByteChunks(value)) => Ok(value), + Some(TestExternalValue::VecBytes(_)) => { + anyhow::bail!("Expected external ByteChunks value") + } + None => anyhow::bail!("Missing external ByteChunks value"), + } + } + + fn finish(&mut self) -> Result<()> { + if self.values.is_empty() { + Ok(()) + } else { + anyhow::bail!("Unused external values") + } + } + } #[test] fn read_from_flatbuffer() -> Result<()> { @@ -314,4 +614,156 @@ mod tests { Ok(()) } + + #[test] + fn embedded_byte_parameters_round_trip_as_distinct_logical_types() { + let mut builder = FlatBufferBuilder::new(); + let parameters = vec![ + ParameterValue::VecBytes(vec![1, 2, 3]), + ParameterValue::ByteChunks(vec![Bytes::from_static(&[4, 5]), Bytes::from_static(&[6])]), + ]; + let encoded = FunctionCall::new( + "bytes".to_string(), + Some(parameters), + FunctionCallType::Host, + ReturnType::VecBytes, + ) + .encode(&mut builder); + + let decoded = FunctionCall::try_from(encoded).unwrap(); + assert_eq!( + decoded.parameters, + Some(vec![ + ParameterValue::VecBytes(vec![1, 2, 3]), + ParameterValue::ByteChunks(vec![Bytes::from_static(&[4, 5, 6])]), + ]) + ); + } + + #[test] + fn external_byte_parameters_round_trip_in_parameter_order() { + let mut builder = FlatBufferBuilder::new(); + let expected_parameters = vec![ + ParameterValue::Int(7), + ParameterValue::UInt(8), + ParameterValue::Long(-9), + ParameterValue::ULong(10), + ParameterValue::Float(1.25), + ParameterValue::Double(2.5), + ParameterValue::Bool(true), + ParameterValue::VecBytes(vec![0xa5; 4096]), + ParameterValue::String("middle".to_string()), + ParameterValue::ByteChunks(vec![ + Bytes::from_static(b"chunk one"), + Bytes::from_static(b" and two"), + ]), + ParameterValue::VecBytes(Vec::new()), + ParameterValue::ByteChunks(Vec::new()), + ]; + let call = FunctionCall::new( + "external_bytes".to_string(), + Some(expected_parameters.clone()), + FunctionCallType::Host, + ReturnType::ByteChunks, + ); + let mut external_values = TestExternalValues::default(); + let encoded = call + .encode_external(&mut builder, &mut external_values) + .unwrap(); + + assert!(encoded.len() < 4096); + assert_eq!( + external_values.values, + VecDeque::from([ + TestExternalValue::VecBytes(vec![0xa5; 4096]), + TestExternalValue::ByteChunks(vec![ + Bytes::from_static(b"chunk one"), + Bytes::from_static(b" and two"), + ]), + TestExternalValue::VecBytes(Vec::new()), + TestExternalValue::ByteChunks(Vec::new()), + ]) + ); + + let encoded_call = size_prefixed_root::(encoded).unwrap(); + let encoded_parameters = encoded_call.parameters().unwrap(); + for (index, length, chunked) in [ + (7, 4096, false), + (9, 17, true), + (10, 0, false), + (11, 0, true), + ] { + let parameter = encoded_parameters.get(index); + assert_eq!(parameter.value_type(), FbParameterValue::hlexternalbytes); + let marker = parameter.value_as_hlexternalbytes().unwrap(); + assert_eq!(marker.length(), length); + assert_eq!(marker.chunked(), chunked); + } + + assert!(FunctionCall::try_from(encoded).is_err()); + let decoded = FunctionCall::decode_external(encoded, &mut external_values).unwrap(); + assert_eq!(decoded.function_name, "external_bytes"); + assert_eq!(decoded.parameters, Some(expected_parameters)); + assert_eq!(decoded.function_call_type(), FunctionCallType::Host); + assert_eq!(decoded.expected_return_type, ReturnType::ByteChunks); + assert!(external_values.values.is_empty()); + } + + #[test] + fn external_encoding_matches_embedded_encoding_without_byte_parameters() { + let call = FunctionCall::new( + "scalars".to_string(), + Some(vec![ + ParameterValue::Int(42), + ParameterValue::String("value".to_string()), + ]), + FunctionCallType::Guest, + ReturnType::Bool, + ); + let mut embedded_builder = FlatBufferBuilder::new(); + let embedded = call.encode(&mut embedded_builder).to_vec(); + + let mut external_builder = FlatBufferBuilder::new(); + let mut external_values = TestExternalValues::default(); + let external = call + .encode_external(&mut external_builder, &mut external_values) + .unwrap(); + + assert_eq!(external, embedded); + assert!(external_values.values.is_empty()); + } + + #[test] + fn external_parameter_decoder_rejects_invalid_value_sequences() { + let mut builder = FlatBufferBuilder::new(); + let call = FunctionCall::new( + "external_bytes".to_string(), + Some(vec![ParameterValue::VecBytes(vec![1, 2, 3])]), + FunctionCallType::Guest, + ReturnType::Void, + ); + let mut encoded_values = TestExternalValues::default(); + let encoded = call + .encode_external(&mut builder, &mut encoded_values) + .unwrap(); + + let mut missing = TestExternalValues::default(); + assert!(FunctionCall::decode_external(encoded, &mut missing).is_err()); + + let mut wrong_type = + TestExternalValues::from_values([TestExternalValue::ByteChunks(vec![ + Bytes::from_static(b"123"), + ])]); + assert!(FunctionCall::decode_external(encoded, &mut wrong_type).is_err()); + + let mut wrong_length = + TestExternalValues::from_values([TestExternalValue::VecBytes(vec![1, 2])]); + assert!(FunctionCall::decode_external(encoded, &mut wrong_length).is_err()); + + let mut extra = TestExternalValues::from_values([ + TestExternalValue::VecBytes(vec![1, 2, 3]), + TestExternalValue::VecBytes(Vec::new()), + ]); + assert!(FunctionCall::decode_external(encoded, &mut extra).is_err()); + } } diff --git a/src/hyperlight_common/src/flatbuffer_wrappers/function_types.rs b/src/hyperlight_common/src/flatbuffer_wrappers/function_types.rs index 8f8248287e..570a7bb44c 100644 --- a/src/hyperlight_common/src/flatbuffer_wrappers/function_types.rs +++ b/src/hyperlight_common/src/flatbuffer_wrappers/function_types.rs @@ -5,18 +5,24 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; use anyhow::{Error, Result, anyhow, bail}; +pub use bytes::Bytes; use flatbuffers::size_prefixed_root; #[cfg(feature = "tracing")] use tracing::{Span, instrument}; +use super::codec::{ExternalValueSink, ExternalValueSource}; use super::guest_error::GuestError; +#[cfg(feature = "fuzzing")] +use super::util::arbitrary_byte_chunks; +use super::util::{byte_chunks_from_bytes, byte_chunks_to_bytes, try_byte_chunks_len}; use crate::flatbuffers::hyperlight::generated::{ FunctionCallResult as FbFunctionCallResult, FunctionCallResultArgs as FbFunctionCallResultArgs, FunctionCallResultType, Parameter, ParameterType as FbParameterType, ParameterValue as FbParameterValue, ReturnType as FbReturnType, ReturnValue as FbReturnValue, - ReturnValueBox, ReturnValueBoxArgs, hlbool, hlboolArgs, hldouble, hldoubleArgs, hlfloat, - hlfloatArgs, hlint, hlintArgs, hllong, hllongArgs, hlsizeprefixedbuffer, - hlsizeprefixedbufferArgs, hlstring, hlstringArgs, hluint, hluintArgs, hlulong, hlulongArgs, + ReturnValueBox, ReturnValueBoxArgs, hlbool, hlboolArgs, hldouble, hldoubleArgs, + hlexternalbytes, hlexternalbytesArgs, hlfloat, hlfloatArgs, hlint, hlintArgs, hllong, + hllongArgs, hlsizeprefixedbuffer, hlsizeprefixedbufferArgs, hlsizeprefixedbytechunks, + hlsizeprefixedbytechunksArgs, hlstring, hlstringArgs, hluint, hluintArgs, hlulong, hlulongArgs, hlvoid, hlvoidArgs, }; @@ -82,6 +88,21 @@ impl FunctionCallResult { FbReturnValue::hlsizeprefixedbuffer, ) } + ReturnValue::ByteChunks(v) => { + let value = byte_chunks_to_bytes(v); + let val = builder.create_vector(value.as_ref()); + let off = hlsizeprefixedbytechunks::create( + builder, + &hlsizeprefixedbytechunksArgs { + value: Some(val), + size: value.len() as i32, + }, + ); + ( + Some(off.as_union_value()), + FbReturnValue::hlsizeprefixedbytechunks, + ) + } ReturnValue::Void(()) => { let off = hlvoid::create(builder, &hlvoidArgs {}); (Some(off.as_union_value()), FbReturnValue::hlvoid) @@ -122,6 +143,61 @@ impl FunctionCallResult { } } } + + /// Encodes byte returns as external markers and sends their payload to + /// `external_values`. + /// + /// Non-byte returns and guest errors retain their existing embedded + /// encoding. + pub fn encode_external<'a, 'b, S>( + &'a self, + builder: &'b mut flatbuffers::FlatBufferBuilder, + external_values: &mut S, + ) -> Result<&'b [u8]> + where + S: ExternalValueSink<'a> + ?Sized, + { + let Ok(return_value) = &self.0 else { + return Ok(self.encode(builder)); + }; + + let (length, chunked) = match return_value { + ReturnValue::VecBytes(value) => { + let length = u64::try_from(value.len()) + .map_err(|_| anyhow!("External VecBytes length does not fit in u64"))?; + external_values.push_bytes(value)?; + (length, false) + } + ReturnValue::ByteChunks(value) => { + let length = try_byte_chunks_len(value) + .ok_or_else(|| anyhow!("External ByteChunks length overflow"))?; + let length = u64::try_from(length) + .map_err(|_| anyhow!("External ByteChunks length does not fit in u64"))?; + external_values.push_chunks(value)?; + (length, true) + } + _ => return Ok(self.encode(builder)), + }; + + let value = hlexternalbytes::create(builder, &hlexternalbytesArgs { length, chunked }); + let return_value = ReturnValueBox::create( + builder, + &ReturnValueBoxArgs { + value: Some(value.as_union_value()), + value_type: FbReturnValue::hlexternalbytes, + }, + ); + let result = FbFunctionCallResult::create( + builder, + &FbFunctionCallResultArgs { + result: Some(return_value.as_union_value()), + result_type: FunctionCallResultType::ReturnValueBox, + }, + ); + builder.finish_size_prefixed(result, None); + Ok(builder.finished_data()) + } + pub fn new(value: core::result::Result) -> Self { FunctionCallResult(value) } @@ -129,6 +205,44 @@ impl FunctionCallResult { pub fn into_inner(self) -> core::result::Result { self.0 } + + /// Decodes a function-call result using `external_values` for external byte + /// markers. + pub fn decode_external(value: &[u8], external_values: &mut S) -> Result + where + S: ExternalValueSource + ?Sized, + { + let function_call_result_fb = size_prefixed_root::(value) + .map_err(|e| anyhow!("Failed to get FunctionCallResult from bytes: {:?}", e))?; + + let result = match function_call_result_fb.result_type() { + FunctionCallResultType::ReturnValueBox => { + let boxed = function_call_result_fb + .result_as_return_value_box() + .ok_or_else(|| { + anyhow!("Failed to get ReturnValueBox from function call result") + })?; + Ok(decode_external_return_value(boxed, external_values)?) + } + FunctionCallResultType::GuestError => { + let guest_error_table = function_call_result_fb + .result_as_guest_error() + .ok_or_else(|| anyhow!("Failed to get GuestError from function call result"))?; + let code = guest_error_table.code(); + let message = guest_error_table + .message() + .map(|s| s.to_string()) + .unwrap_or_default(); + Err(GuestError::new(code.into(), message)) + } + other => { + bail!("Unexpected function call result type: {:?}", other) + } + }; + + external_values.finish()?; + Ok(FunctionCallResult(result)) + } } impl TryFrom<&[u8]> for FunctionCallResult { @@ -191,6 +305,13 @@ pub enum ParameterValue { Bool(bool), /// `Vec` VecBytes(Vec), + /// One logical byte sequence represented as chunks. + /// + /// Chunk boundaries are a storage detail and may change during transport. + /// They do not delimit messages or values. + ByteChunks( + #[cfg_attr(feature = "fuzzing", arbitrary(with = arbitrary_byte_chunks))] Vec, + ), } /// Supported parameter types for function calling. @@ -215,6 +336,8 @@ pub enum ParameterType { Bool, /// `Vec` VecBytes, + /// One logical byte sequence represented as chunks. + ByteChunks, } /// Supported return types with values from function calling. @@ -240,6 +363,11 @@ pub enum ReturnValue { Void(()), /// `Vec` VecBytes(Vec), + /// One logical byte sequence represented as chunks. + /// + /// Chunk boundaries are a storage detail and may change during transport. + /// They do not delimit messages or values. + ByteChunks(Vec), } /// Supported return types from function calling. @@ -268,6 +396,100 @@ pub enum ReturnType { Void, /// `Vec` VecBytes, + /// One logical byte sequence represented as chunks. + ByteChunks, +} + +pub(crate) fn decode_external_parameter_value( + parameter: Parameter<'_>, + external_values: &mut S, +) -> Result +where + S: ExternalValueSource + ?Sized, +{ + if parameter.value_type() != FbParameterValue::hlexternalbytes { + return parameter.try_into(); + } + + let marker = parameter + .value_as_hlexternalbytes() + .ok_or_else(|| anyhow!("Failed to get external byte parameter marker"))?; + let length = usize::try_from(marker.length()).map_err(|_| { + anyhow!( + "External byte parameter length {} does not fit in usize", + marker.length() + ) + })?; + + if marker.chunked() { + let value = external_values.take_chunks(length)?; + let actual_length = try_byte_chunks_len(&value) + .ok_or_else(|| anyhow!("External ByteChunks parameter length overflow"))?; + if actual_length != length { + bail!( + "External ByteChunks parameter length mismatch: declared {}, received {}", + length, + actual_length + ); + } + Ok(ParameterValue::ByteChunks(value)) + } else { + let value = external_values.take_bytes(length)?; + if value.len() != length { + bail!( + "External VecBytes parameter length mismatch: declared {}, received {}", + length, + value.len() + ); + } + Ok(ParameterValue::VecBytes(value)) + } +} + +fn decode_external_return_value( + return_value: ReturnValueBox<'_>, + external_values: &mut S, +) -> Result +where + S: ExternalValueSource + ?Sized, +{ + if return_value.value_type() != FbReturnValue::hlexternalbytes { + return return_value.try_into(); + } + + let marker = return_value + .value_as_hlexternalbytes() + .ok_or_else(|| anyhow!("Failed to get external byte return marker"))?; + let length = usize::try_from(marker.length()).map_err(|_| { + anyhow!( + "External byte return length {} does not fit in usize", + marker.length() + ) + })?; + + if marker.chunked() { + let value = external_values.take_chunks(length)?; + let actual_length = try_byte_chunks_len(&value) + .ok_or_else(|| anyhow!("External ByteChunks return length overflow"))?; + if actual_length != length { + bail!( + "External ByteChunks return length mismatch: declared {}, received {}", + length, + actual_length + ); + } + Ok(ReturnValue::ByteChunks(value)) + } else { + let value = external_values.take_bytes(length)?; + if value.len() != length { + bail!( + "External VecBytes return length mismatch: declared {}, received {}", + length, + value.len() + ); + } + Ok(ReturnValue::VecBytes(value)) + } } impl From<&ParameterValue> for ParameterType { @@ -283,6 +505,7 @@ impl From<&ParameterValue> for ParameterType { ParameterValue::String(_) => ParameterType::String, ParameterValue::Bool(_) => ParameterType::Bool, ParameterValue::VecBytes(_) => ParameterType::VecBytes, + ParameterValue::ByteChunks(_) => ParameterType::ByteChunks, } } } @@ -321,6 +544,14 @@ impl TryFrom> for ParameterValue { FbParameterValue::hlvecbytes => param.value_as_hlvecbytes().map(|hlvecbytes| { ParameterValue::VecBytes(hlvecbytes.value().unwrap_or_default().bytes().to_vec()) }), + FbParameterValue::hlbytechunks => param.value_as_hlbytechunks().map(|hlbytechunks| { + ParameterValue::ByteChunks(byte_chunks_from_bytes(Bytes::copy_from_slice( + hlbytechunks.value().unwrap_or_default().bytes(), + ))) + }), + FbParameterValue::hlexternalbytes => { + bail!("External byte parameter requires an external value source") + } other => { bail!("Unexpected flatbuffer parameter value type: {:?}", other); } @@ -342,6 +573,7 @@ impl From for FbParameterType { ParameterType::String => FbParameterType::hlstring, ParameterType::Bool => FbParameterType::hlbool, ParameterType::VecBytes => FbParameterType::hlvecbytes, + ParameterType::ByteChunks => FbParameterType::hlbytechunks, } } } @@ -360,6 +592,7 @@ impl From for FbReturnType { ReturnType::Bool => FbReturnType::hlbool, ReturnType::Void => FbReturnType::hlvoid, ReturnType::VecBytes => FbReturnType::hlsizeprefixedbuffer, + ReturnType::ByteChunks => FbReturnType::hlbytechunks, } } } @@ -378,6 +611,7 @@ impl TryFrom for ParameterType { FbParameterType::hlstring => Ok(ParameterType::String), FbParameterType::hlbool => Ok(ParameterType::Bool), FbParameterType::hlvecbytes => Ok(ParameterType::VecBytes), + FbParameterType::hlbytechunks => Ok(ParameterType::ByteChunks), _ => { bail!("Unexpected flatbuffer parameter type: {:?}", value) } @@ -400,6 +634,7 @@ impl TryFrom for ReturnType { FbReturnType::hlbool => Ok(ReturnType::Bool), FbReturnType::hlvoid => Ok(ReturnType::Void), FbReturnType::hlsizeprefixedbuffer => Ok(ReturnType::VecBytes), + FbReturnType::hlbytechunks => Ok(ReturnType::ByteChunks), _ => { bail!("Unexpected flatbuffer return type: {:?}", value) } @@ -524,6 +759,17 @@ impl TryFrom for Vec { } } +impl TryFrom for Vec { + type Error = Error; + + fn try_from(value: ParameterValue) -> Result { + match value { + ParameterValue::ByteChunks(v) => Ok(v), + _ => bail!("Unexpected parameter value type: {:?}", value), + } + } +} + impl TryFrom for i32 { type Error = Error; #[cfg_attr(feature = "tracing", instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace"))] @@ -641,6 +887,17 @@ impl TryFrom for Vec { } } +impl TryFrom for Vec { + type Error = Error; + + fn try_from(value: ReturnValue) -> Result { + match value { + ReturnValue::ByteChunks(v) => Ok(v), + _ => bail!("Unexpected return value type: {:?}", value), + } + } +} + impl TryFrom for () { type Error = Error; #[cfg_attr(feature = "tracing", instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace"))] @@ -718,6 +975,17 @@ impl TryFrom> for ReturnValue { }; Ok(ReturnValue::VecBytes(hlvecbytes.unwrap_or(Vec::new()))) } + FbReturnValue::hlsizeprefixedbytechunks => { + let value = return_value_box + .value_as_hlsizeprefixedbytechunks() + .and_then(|value| value.value()) + .map(|value| byte_chunks_from_bytes(Bytes::copy_from_slice(value.bytes()))) + .unwrap_or_default(); + Ok(ReturnValue::ByteChunks(value)) + } + FbReturnValue::hlexternalbytes => { + bail!("External byte return requires an external value source") + } other => { bail!("Unexpected flatbuffer return value type: {:?}", other) } @@ -914,6 +1182,35 @@ impl TryFrom<&ReturnValue> for Vec { builder.finish_size_prefixed(fcr, None); builder.finished_data().to_vec() } + ReturnValue::ByteChunks(v) => { + let off = { + let value = byte_chunks_to_bytes(v); + let val = builder.create_vector(value.as_ref()); + hlsizeprefixedbytechunks::create( + &mut builder, + &hlsizeprefixedbytechunksArgs { + value: Some(val), + size: value.len() as i32, + }, + ) + }; + let rv_box = ReturnValueBox::create( + &mut builder, + &ReturnValueBoxArgs { + value: Some(off.as_union_value()), + value_type: FbReturnValue::hlsizeprefixedbytechunks, + }, + ); + let fcr = FbFunctionCallResult::create( + &mut builder, + &FbFunctionCallResultArgs { + result: Some(rv_box.as_union_value()), + result_type: FunctionCallResultType::ReturnValueBox, + }, + ); + builder.finish_size_prefixed(fcr, None); + builder.finished_data().to_vec() + } ReturnValue::Void(()) => { let off = hlvoid::create(&mut builder, &hlvoidArgs {}); let rv_box = ReturnValueBox::create( @@ -941,10 +1238,78 @@ impl TryFrom<&ReturnValue> for Vec { #[cfg(test)] mod tests { + use alloc::collections::VecDeque; + use alloc::vec; + use flatbuffers::FlatBufferBuilder; use super::super::guest_error::ErrorCode; + use super::super::util::{byte_chunks_to_vec, get_flatbuffer_result}; use super::*; + use crate::flatbuffers::hyperlight::generated::{hlexternalbytes, hlexternalbytesArgs}; + + #[derive(Debug, Clone, PartialEq)] + enum TestExternalValue { + VecBytes(Vec), + ByteChunks(Vec), + } + + #[derive(Default)] + struct TestExternalValues { + values: VecDeque, + } + + impl TestExternalValues { + fn from_values(values: impl IntoIterator) -> Self { + Self { + values: values.into_iter().collect(), + } + } + } + + impl<'a> ExternalValueSink<'a> for TestExternalValues { + fn push_bytes(&mut self, value: &'a [u8]) -> Result<()> { + self.values + .push_back(TestExternalValue::VecBytes(value.to_vec())); + Ok(()) + } + + fn push_chunks(&mut self, value: &'a [Bytes]) -> Result<()> { + self.values + .push_back(TestExternalValue::ByteChunks(value.to_vec())); + Ok(()) + } + } + + impl ExternalValueSource for TestExternalValues { + fn take_bytes(&mut self, _length: usize) -> Result> { + match self.values.pop_front() { + Some(TestExternalValue::VecBytes(value)) => Ok(value), + Some(TestExternalValue::ByteChunks(_)) => { + anyhow::bail!("Expected external VecBytes value") + } + None => anyhow::bail!("Missing external VecBytes value"), + } + } + + fn take_chunks(&mut self, _length: usize) -> Result> { + match self.values.pop_front() { + Some(TestExternalValue::ByteChunks(value)) => Ok(value), + Some(TestExternalValue::VecBytes(_)) => { + anyhow::bail!("Expected external ByteChunks value") + } + None => anyhow::bail!("Missing external ByteChunks value"), + } + } + + fn finish(&mut self) -> Result<()> { + if self.values.is_empty() { + Ok(()) + } else { + anyhow::bail!("Unused external values") + } + } + } #[test] fn encode_success_result() { @@ -970,4 +1335,142 @@ mod tests { assert_eq!(error.code, test_error.code); assert_eq!(error.message, test_error.message); } + + #[test] + fn embedded_byte_chunks_return_round_trips() { + let mut builder = FlatBufferBuilder::new(); + let expected = vec![Bytes::from_static(b"hello"), Bytes::from_static(b" world")]; + let encoded = + FunctionCallResult::new(Ok(ReturnValue::ByteChunks(expected))).encode(&mut builder); + + let decoded = FunctionCallResult::try_from(encoded) + .unwrap() + .into_inner() + .unwrap(); + let ReturnValue::ByteChunks(decoded) = decoded else { + panic!("expected byte chunks return value"); + }; + assert_eq!(byte_chunks_to_vec(&decoded), b"hello world"); + } + + #[test] + fn direct_byte_chunks_return_encoding_preserves_logical_type() { + let encoded = get_flatbuffer_result(vec![ + Bytes::from_static(b"hello"), + Bytes::from_static(b" world"), + ]); + + let decoded = FunctionCallResult::try_from(encoded.as_slice()) + .unwrap() + .into_inner() + .unwrap(); + assert!(matches!(decoded, ReturnValue::ByteChunks(_))); + } + + #[test] + fn external_bytes_marks_chunked_values_only() { + fn round_trip(chunked: bool) -> bool { + let mut builder = FlatBufferBuilder::new(); + let value = hlexternalbytes::create( + &mut builder, + &hlexternalbytesArgs { + length: 42, + chunked, + }, + ); + builder.finish(value, None); + let value = flatbuffers::root::(builder.finished_data()).unwrap(); + + assert_eq!(value.length(), 42); + value.chunked() + } + + assert!(!round_trip(false)); + assert!(round_trip(true)); + } + + #[test] + fn external_byte_returns_round_trip_without_embedding_payloads() { + for expected in [ + ReturnValue::VecBytes(vec![0xa5; 4096]), + ReturnValue::ByteChunks(vec![ + Bytes::from_static(b"chunk one"), + Bytes::from_static(b" and two"), + ]), + ReturnValue::VecBytes(Vec::new()), + ReturnValue::ByteChunks(Vec::new()), + ] { + let mut builder = FlatBufferBuilder::new(); + let mut external_values = TestExternalValues::default(); + let encoded = FunctionCallResult::new(Ok(expected.clone())) + .encode_external(&mut builder, &mut external_values) + .unwrap(); + + assert!(encoded.len() < 4096); + let encoded_result = size_prefixed_root::(encoded).unwrap(); + let return_value = encoded_result.result_as_return_value_box().unwrap(); + assert_eq!(return_value.value_type(), FbReturnValue::hlexternalbytes); + let marker = return_value.value_as_hlexternalbytes().unwrap(); + let (length, chunked) = match &expected { + ReturnValue::VecBytes(value) => (value.len(), false), + ReturnValue::ByteChunks(value) => (try_byte_chunks_len(value).unwrap(), true), + _ => unreachable!(), + }; + assert_eq!(marker.length(), length as u64); + assert_eq!(marker.chunked(), chunked); + + assert!(FunctionCallResult::try_from(encoded).is_err()); + let decoded = FunctionCallResult::decode_external(encoded, &mut external_values) + .unwrap() + .into_inner() + .unwrap(); + assert_eq!(decoded, expected); + assert!(external_values.values.is_empty()); + } + } + + #[test] + fn external_return_decoder_rejects_invalid_values() { + let mut builder = FlatBufferBuilder::new(); + let mut encoded_values = TestExternalValues::default(); + let encoded = + FunctionCallResult::new(Ok(ReturnValue::ByteChunks(vec![Bytes::from_static( + b"123", + )]))) + .encode_external(&mut builder, &mut encoded_values) + .unwrap(); + + let mut missing = TestExternalValues::default(); + assert!(FunctionCallResult::decode_external(encoded, &mut missing).is_err()); + + let mut wrong_type = + TestExternalValues::from_values([TestExternalValue::VecBytes(vec![1, 2, 3])]); + assert!(FunctionCallResult::decode_external(encoded, &mut wrong_type).is_err()); + + let mut wrong_length = + TestExternalValues::from_values([TestExternalValue::ByteChunks(vec![ + Bytes::from_static(b"12"), + ])]); + assert!(FunctionCallResult::decode_external(encoded, &mut wrong_length).is_err()); + } + + #[test] + fn external_result_decoder_rejects_unused_values() { + let result = FunctionCallResult::new(Ok(ReturnValue::Int(42))); + let mut embedded_builder = FlatBufferBuilder::new(); + let embedded = result.encode(&mut embedded_builder).to_vec(); + + let mut external_builder = FlatBufferBuilder::new(); + let mut external_values = TestExternalValues::default(); + let external = result + .encode_external(&mut external_builder, &mut external_values) + .unwrap(); + assert_eq!(external, embedded); + assert!(external_values.values.is_empty()); + + external_values + .values + .push_back(TestExternalValue::VecBytes(Vec::new())); + assert!(FunctionCallResult::decode_external(external, &mut external_values).is_err()); + } } diff --git a/src/hyperlight_common/src/flatbuffer_wrappers/mod.rs b/src/hyperlight_common/src/flatbuffer_wrappers/mod.rs index 54d8d45582..4001d600fb 100644 --- a/src/hyperlight_common/src/flatbuffer_wrappers/mod.rs +++ b/src/hyperlight_common/src/flatbuffer_wrappers/mod.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2025 The Hyperlight Authors. +mod codec; pub mod function_call; pub mod function_types; pub mod guest_error; @@ -16,3 +17,5 @@ pub mod host_function_definition; /// cbindgen:ignore pub mod host_function_details; pub mod util; + +pub use codec::{ExternalValueSink, ExternalValueSource}; diff --git a/src/hyperlight_common/src/flatbuffer_wrappers/util.rs b/src/hyperlight_common/src/flatbuffer_wrappers/util.rs index b9638dcef5..dc9134e6fe 100644 --- a/src/hyperlight_common/src/flatbuffer_wrappers/util.rs +++ b/src/hyperlight_common/src/flatbuffer_wrappers/util.rs @@ -1,8 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2025 The Hyperlight Authors. +use alloc::vec; use alloc::vec::Vec; +use bytes::Bytes; use flatbuffers::FlatBufferBuilder; use crate::flatbuffer_wrappers::function_types::ParameterValue; @@ -13,7 +15,9 @@ use crate::flatbuffers::hyperlight::generated::{ hldouble as Fbhldouble, hldoubleArgs as FbhldoubleArgs, hlfloat as Fbhlfloat, hlfloatArgs as FbhlfloatArgs, hlint as Fbhlint, hlintArgs as FbhlintArgs, hllong as Fbhllong, hllongArgs as FbhllongArgs, hlsizeprefixedbuffer as Fbhlsizeprefixedbuffer, - hlsizeprefixedbufferArgs as FbhlsizeprefixedbufferArgs, hlstring as Fbhlstring, + hlsizeprefixedbufferArgs as FbhlsizeprefixedbufferArgs, + hlsizeprefixedbytechunks as Fbhlsizeprefixedbytechunks, + hlsizeprefixedbytechunksArgs as FbhlsizeprefixedbytechunksArgs, hlstring as Fbhlstring, hlstringArgs as FbhlstringArgs, hluint as Fbhluint, hluintArgs as FbhluintArgs, hlulong as Fbhlulong, hlulongArgs as FbhlulongArgs, hlvoid as Fbhlvoid, hlvoidArgs as FbhlvoidArgs, @@ -100,6 +104,31 @@ impl FlatbufferSerializable for &[u8] { } } +impl FlatbufferSerializable for Vec { + fn serialize(&self, builder: &mut FlatBufferBuilder) -> FbFunctionCallResultArgs { + let value = byte_chunks_to_bytes(self); + let vec_off = builder.create_vector(value.as_ref()); + let buf_off = Fbhlsizeprefixedbytechunks::create( + builder, + &FbhlsizeprefixedbytechunksArgs { + size: value.len() as i32, + value: Some(vec_off), + }, + ); + let rv_box = ReturnValueBox::create( + builder, + &ReturnValueBoxArgs { + value_type: FbReturnValue::hlsizeprefixedbytechunks, + value: Some(buf_off.as_union_value()), + }, + ); + FbFunctionCallResultArgs { + result_type: FbFunctionCallResultType::ReturnValueBox, + result: Some(rv_box.as_union_value()), + } + } +} + impl FlatbufferSerializable for f32 { fn serialize(&self, builder: &mut FlatBufferBuilder) -> FbFunctionCallResultArgs { let off = Fbhlfloat::create(builder, &FbhlfloatArgs { value: *self }); @@ -247,6 +276,7 @@ pub fn estimate_flatbuffer_capacity(function_name: &str, args: &[ParameterValue] estimated_capacity += match arg { ParameterValue::String(s) => s.len() + 20, ParameterValue::VecBytes(v) => v.len() + 20, + ParameterValue::ByteChunks(v) => byte_chunks_len(v) + 20, ParameterValue::Int(_) | ParameterValue::UInt(_) => 16, ParameterValue::Long(_) | ParameterValue::ULong(_) => 20, ParameterValue::Float(_) => 16, @@ -259,6 +289,61 @@ pub fn estimate_flatbuffer_capacity(function_name: &str, args: &[ParameterValue] estimated_capacity.next_power_of_two() } +/// Wrap contiguous bytes as one chunk without copying. +pub fn byte_chunks_from_bytes(value: Bytes) -> Vec { + if value.is_empty() { + Vec::new() + } else { + vec![value] + } +} + +/// Wrap a contiguous vector as one chunk without copying. +pub fn byte_chunks_from_vec(value: Vec) -> Vec { + byte_chunks_from_bytes(Bytes::from(value)) +} + +/// Return the complete logical length of a chunked byte value. +pub(crate) fn byte_chunks_len(value: &[Bytes]) -> usize { + value.iter().map(Bytes::len).sum() +} + +/// Return the complete logical length, or `None` if the sum overflows. +pub(crate) fn try_byte_chunks_len(value: &[Bytes]) -> Option { + value + .iter() + .try_fold(0usize, |length, chunk| length.checked_add(chunk.len())) +} + +/// Materialize byte chunks as contiguous [`Bytes`]. +/// +/// This is O(1) for zero or one chunk and copies once for multiple chunks. +pub fn byte_chunks_to_bytes(value: &[Bytes]) -> Bytes { + match value { + [] => Bytes::new(), + [chunk] => chunk.clone(), + chunks => Bytes::from(byte_chunks_to_vec(chunks)), + } +} + +/// Materialize byte chunks as one contiguous vector. +/// +/// This always allocates a new vector and copies the complete logical value. +pub fn byte_chunks_to_vec(value: &[Bytes]) -> Vec { + let mut output = Vec::with_capacity(byte_chunks_len(value)); + for chunk in value { + output.extend_from_slice(chunk); + } + output +} + +#[cfg(feature = "fuzzing")] +pub(crate) fn arbitrary_byte_chunks( + input: &mut arbitrary::Unstructured<'_>, +) -> arbitrary::Result> { + as arbitrary::Arbitrary>::arbitrary(input).map(byte_chunks_from_vec) +} + #[cfg(test)] mod tests { use alloc::string::ToString; diff --git a/src/hyperlight_common/src/flatbuffers/hyperlight/generated/hlbytechunks_generated.rs b/src/hyperlight_common/src/flatbuffers/hyperlight/generated/hlbytechunks_generated.rs new file mode 100644 index 0000000000..d4f1940fd9 --- /dev/null +++ b/src/hyperlight_common/src/flatbuffers/hyperlight/generated/hlbytechunks_generated.rs @@ -0,0 +1,124 @@ +// automatically generated by the FlatBuffers compiler, do not modify +// @generated +extern crate alloc; +extern crate flatbuffers; +use alloc::boxed::Box; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use core::cmp::Ordering; +use core::mem; + +use self::flatbuffers::{EndianScalar, Follow}; +use super::*; +pub enum hlbytechunksOffset {} +#[derive(Copy, Clone, PartialEq)] + +pub struct hlbytechunks<'a> { + pub _tab: flatbuffers::Table<'a>, +} + +impl<'a> flatbuffers::Follow<'a> for hlbytechunks<'a> { + type Inner = hlbytechunks<'a>; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { + _tab: unsafe { flatbuffers::Table::new(buf, loc) }, + } + } +} + +impl<'a> hlbytechunks<'a> { + pub const VT_VALUE: flatbuffers::VOffsetT = 4; + + #[inline] + pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self { + hlbytechunks { _tab: table } + } + #[allow(unused_mut)] + pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr, A: flatbuffers::Allocator + 'bldr>( + _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr, A>, + args: &'args hlbytechunksArgs<'args>, + ) -> flatbuffers::WIPOffset> { + let mut builder = hlbytechunksBuilder::new(_fbb); + if let Some(x) = args.value { + builder.add_value(x); + } + builder.finish() + } + + #[inline] + pub fn value(&self) -> Option> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { + self._tab + .get::>>( + hlbytechunks::VT_VALUE, + None, + ) + } + } +} + +impl flatbuffers::Verifiable for hlbytechunks<'_> { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, + pos: usize, + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + v.visit_table(pos)? + .visit_field::>>( + "value", + Self::VT_VALUE, + false, + )? + .finish(); + Ok(()) + } +} +pub struct hlbytechunksArgs<'a> { + pub value: Option>>, +} +impl<'a> Default for hlbytechunksArgs<'a> { + #[inline] + fn default() -> Self { + hlbytechunksArgs { value: None } + } +} + +pub struct hlbytechunksBuilder<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> { + fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a, A>, + start_: flatbuffers::WIPOffset, +} +impl<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> hlbytechunksBuilder<'a, 'b, A> { + #[inline] + pub fn add_value(&mut self, value: flatbuffers::WIPOffset>) { + self.fbb_ + .push_slot_always::>(hlbytechunks::VT_VALUE, value); + } + #[inline] + pub fn new( + _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a, A>, + ) -> hlbytechunksBuilder<'a, 'b, A> { + let start = _fbb.start_table(); + hlbytechunksBuilder { + fbb_: _fbb, + start_: start, + } + } + #[inline] + pub fn finish(self) -> flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + flatbuffers::WIPOffset::new(o.value()) + } +} + +impl core::fmt::Debug for hlbytechunks<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut ds = f.debug_struct("hlbytechunks"); + ds.field("value", &self.value()); + ds.finish() + } +} diff --git a/src/hyperlight_common/src/flatbuffers/hyperlight/generated/hlexternalbytes_generated.rs b/src/hyperlight_common/src/flatbuffers/hyperlight/generated/hlexternalbytes_generated.rs new file mode 100644 index 0000000000..d984c1bb85 --- /dev/null +++ b/src/hyperlight_common/src/flatbuffers/hyperlight/generated/hlexternalbytes_generated.rs @@ -0,0 +1,140 @@ +// automatically generated by the FlatBuffers compiler, do not modify +// @generated +extern crate alloc; +extern crate flatbuffers; +use alloc::boxed::Box; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use core::cmp::Ordering; +use core::mem; + +use self::flatbuffers::{EndianScalar, Follow}; +use super::*; +pub enum hlexternalbytesOffset {} +#[derive(Copy, Clone, PartialEq)] + +pub struct hlexternalbytes<'a> { + pub _tab: flatbuffers::Table<'a>, +} + +impl<'a> flatbuffers::Follow<'a> for hlexternalbytes<'a> { + type Inner = hlexternalbytes<'a>; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { + _tab: unsafe { flatbuffers::Table::new(buf, loc) }, + } + } +} + +impl<'a> hlexternalbytes<'a> { + pub const VT_LENGTH: flatbuffers::VOffsetT = 4; + pub const VT_CHUNKED: flatbuffers::VOffsetT = 6; + + #[inline] + pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self { + hlexternalbytes { _tab: table } + } + #[allow(unused_mut)] + pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr, A: flatbuffers::Allocator + 'bldr>( + _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr, A>, + args: &'args hlexternalbytesArgs, + ) -> flatbuffers::WIPOffset> { + let mut builder = hlexternalbytesBuilder::new(_fbb); + builder.add_length(args.length); + builder.add_chunked(args.chunked); + builder.finish() + } + + #[inline] + pub fn length(&self) -> u64 { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { + self._tab + .get::(hlexternalbytes::VT_LENGTH, Some(0)) + .unwrap() + } + } + #[inline] + pub fn chunked(&self) -> bool { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { + self._tab + .get::(hlexternalbytes::VT_CHUNKED, Some(false)) + .unwrap() + } + } +} + +impl flatbuffers::Verifiable for hlexternalbytes<'_> { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, + pos: usize, + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + v.visit_table(pos)? + .visit_field::("length", Self::VT_LENGTH, false)? + .visit_field::("chunked", Self::VT_CHUNKED, false)? + .finish(); + Ok(()) + } +} +pub struct hlexternalbytesArgs { + pub length: u64, + pub chunked: bool, +} +impl<'a> Default for hlexternalbytesArgs { + #[inline] + fn default() -> Self { + hlexternalbytesArgs { + length: 0, + chunked: false, + } + } +} + +pub struct hlexternalbytesBuilder<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> { + fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a, A>, + start_: flatbuffers::WIPOffset, +} +impl<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> hlexternalbytesBuilder<'a, 'b, A> { + #[inline] + pub fn add_length(&mut self, length: u64) { + self.fbb_ + .push_slot::(hlexternalbytes::VT_LENGTH, length, 0); + } + #[inline] + pub fn add_chunked(&mut self, chunked: bool) { + self.fbb_ + .push_slot::(hlexternalbytes::VT_CHUNKED, chunked, false); + } + #[inline] + pub fn new( + _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a, A>, + ) -> hlexternalbytesBuilder<'a, 'b, A> { + let start = _fbb.start_table(); + hlexternalbytesBuilder { + fbb_: _fbb, + start_: start, + } + } + #[inline] + pub fn finish(self) -> flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + flatbuffers::WIPOffset::new(o.value()) + } +} + +impl core::fmt::Debug for hlexternalbytes<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut ds = f.debug_struct("hlexternalbytes"); + ds.field("length", &self.length()); + ds.field("chunked", &self.chunked()); + ds.finish() + } +} diff --git a/src/hyperlight_common/src/flatbuffers/hyperlight/generated/hlsizeprefixedbytechunks_generated.rs b/src/hyperlight_common/src/flatbuffers/hyperlight/generated/hlsizeprefixedbytechunks_generated.rs new file mode 100644 index 0000000000..54661a89f0 --- /dev/null +++ b/src/hyperlight_common/src/flatbuffers/hyperlight/generated/hlsizeprefixedbytechunks_generated.rs @@ -0,0 +1,150 @@ +// automatically generated by the FlatBuffers compiler, do not modify +// @generated +extern crate alloc; +extern crate flatbuffers; +use alloc::boxed::Box; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use core::cmp::Ordering; +use core::mem; + +use self::flatbuffers::{EndianScalar, Follow}; +use super::*; +pub enum hlsizeprefixedbytechunksOffset {} +#[derive(Copy, Clone, PartialEq)] + +pub struct hlsizeprefixedbytechunks<'a> { + pub _tab: flatbuffers::Table<'a>, +} + +impl<'a> flatbuffers::Follow<'a> for hlsizeprefixedbytechunks<'a> { + type Inner = hlsizeprefixedbytechunks<'a>; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { + _tab: unsafe { flatbuffers::Table::new(buf, loc) }, + } + } +} + +impl<'a> hlsizeprefixedbytechunks<'a> { + pub const VT_SIZE: flatbuffers::VOffsetT = 4; + pub const VT_VALUE: flatbuffers::VOffsetT = 6; + + #[inline] + pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self { + hlsizeprefixedbytechunks { _tab: table } + } + #[allow(unused_mut)] + pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr, A: flatbuffers::Allocator + 'bldr>( + _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr, A>, + args: &'args hlsizeprefixedbytechunksArgs<'args>, + ) -> flatbuffers::WIPOffset> { + let mut builder = hlsizeprefixedbytechunksBuilder::new(_fbb); + if let Some(x) = args.value { + builder.add_value(x); + } + builder.add_size(args.size); + builder.finish() + } + + #[inline] + pub fn size(&self) -> i32 { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { + self._tab + .get::(hlsizeprefixedbytechunks::VT_SIZE, Some(0)) + .unwrap() + } + } + #[inline] + pub fn value(&self) -> Option> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { + self._tab + .get::>>( + hlsizeprefixedbytechunks::VT_VALUE, + None, + ) + } + } +} + +impl flatbuffers::Verifiable for hlsizeprefixedbytechunks<'_> { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, + pos: usize, + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + v.visit_table(pos)? + .visit_field::("size", Self::VT_SIZE, false)? + .visit_field::>>( + "value", + Self::VT_VALUE, + false, + )? + .finish(); + Ok(()) + } +} +pub struct hlsizeprefixedbytechunksArgs<'a> { + pub size: i32, + pub value: Option>>, +} +impl<'a> Default for hlsizeprefixedbytechunksArgs<'a> { + #[inline] + fn default() -> Self { + hlsizeprefixedbytechunksArgs { + size: 0, + value: None, + } + } +} + +pub struct hlsizeprefixedbytechunksBuilder<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> { + fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a, A>, + start_: flatbuffers::WIPOffset, +} +impl<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> hlsizeprefixedbytechunksBuilder<'a, 'b, A> { + #[inline] + pub fn add_size(&mut self, size: i32) { + self.fbb_ + .push_slot::(hlsizeprefixedbytechunks::VT_SIZE, size, 0); + } + #[inline] + pub fn add_value(&mut self, value: flatbuffers::WIPOffset>) { + self.fbb_.push_slot_always::>( + hlsizeprefixedbytechunks::VT_VALUE, + value, + ); + } + #[inline] + pub fn new( + _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a, A>, + ) -> hlsizeprefixedbytechunksBuilder<'a, 'b, A> { + let start = _fbb.start_table(); + hlsizeprefixedbytechunksBuilder { + fbb_: _fbb, + start_: start, + } + } + #[inline] + pub fn finish(self) -> flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + flatbuffers::WIPOffset::new(o.value()) + } +} + +impl core::fmt::Debug for hlsizeprefixedbytechunks<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut ds = f.debug_struct("hlsizeprefixedbytechunks"); + ds.field("size", &self.size()); + ds.field("value", &self.value()); + ds.finish() + } +} diff --git a/src/hyperlight_common/src/flatbuffers/hyperlight/generated/parameter_generated.rs b/src/hyperlight_common/src/flatbuffers/hyperlight/generated/parameter_generated.rs index b0e803ec52..33f7c98190 100644 --- a/src/hyperlight_common/src/flatbuffers/hyperlight/generated/parameter_generated.rs +++ b/src/hyperlight_common/src/flatbuffers/hyperlight/generated/parameter_generated.rs @@ -198,6 +198,34 @@ impl<'a> Parameter<'a> { None } } + + #[inline] + #[allow(non_snake_case)] + pub fn value_as_hlexternalbytes(&self) -> Option> { + if self.value_type() == ParameterValue::hlexternalbytes { + let u = self.value(); + // Safety: + // Created from a valid Table for this object + // Which contains a valid union in this slot + Some(unsafe { hlexternalbytes::init_from_table(u) }) + } else { + None + } + } + + #[inline] + #[allow(non_snake_case)] + pub fn value_as_hlbytechunks(&self) -> Option> { + if self.value_type() == ParameterValue::hlbytechunks { + let u = self.value(); + // Safety: + // Created from a valid Table for this object + // Which contains a valid union in this slot + Some(unsafe { hlbytechunks::init_from_table(u) }) + } else { + None + } + } } impl flatbuffers::Verifiable for Parameter<'_> { @@ -260,6 +288,16 @@ impl flatbuffers::Verifiable for Parameter<'_> { "ParameterValue::hlvecbytes", pos, ), + ParameterValue::hlexternalbytes => v + .verify_union_variant::>( + "ParameterValue::hlexternalbytes", + pos, + ), + ParameterValue::hlbytechunks => v + .verify_union_variant::>( + "ParameterValue::hlbytechunks", + pos, + ), _ => Ok(()), }, )? @@ -410,6 +448,26 @@ impl core::fmt::Debug for Parameter<'_> { ) } } + ParameterValue::hlexternalbytes => { + if let Some(x) = self.value_as_hlexternalbytes() { + ds.field("value", &x) + } else { + ds.field( + "value", + &"InvalidFlatbuffer: Union discriminant does not match value.", + ) + } + } + ParameterValue::hlbytechunks => { + if let Some(x) = self.value_as_hlbytechunks() { + ds.field("value", &x) + } else { + ds.field( + "value", + &"InvalidFlatbuffer: Union discriminant does not match value.", + ) + } + } _ => { let x: Option<()> = None; ds.field("value", &x) diff --git a/src/hyperlight_common/src/flatbuffers/hyperlight/generated/parameter_type_generated.rs b/src/hyperlight_common/src/flatbuffers/hyperlight/generated/parameter_type_generated.rs index cf46560b12..cd280599a0 100644 --- a/src/hyperlight_common/src/flatbuffers/hyperlight/generated/parameter_type_generated.rs +++ b/src/hyperlight_common/src/flatbuffers/hyperlight/generated/parameter_type_generated.rs @@ -19,13 +19,13 @@ pub const ENUM_MIN_PARAMETER_TYPE: u8 = 0; since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021." )] -pub const ENUM_MAX_PARAMETER_TYPE: u8 = 8; +pub const ENUM_MAX_PARAMETER_TYPE: u8 = 9; #[deprecated( since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021." )] #[allow(non_camel_case_types)] -pub const ENUM_VALUES_PARAMETER_TYPE: [ParameterType; 9] = [ +pub const ENUM_VALUES_PARAMETER_TYPE: [ParameterType; 10] = [ ParameterType::hlint, ParameterType::hluint, ParameterType::hllong, @@ -35,6 +35,7 @@ pub const ENUM_VALUES_PARAMETER_TYPE: [ParameterType; 9] = [ ParameterType::hlstring, ParameterType::hlbool, ParameterType::hlvecbytes, + ParameterType::hlbytechunks, ]; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] @@ -51,9 +52,10 @@ impl ParameterType { pub const hlstring: Self = Self(6); pub const hlbool: Self = Self(7); pub const hlvecbytes: Self = Self(8); + pub const hlbytechunks: Self = Self(9); pub const ENUM_MIN: u8 = 0; - pub const ENUM_MAX: u8 = 8; + pub const ENUM_MAX: u8 = 9; pub const ENUM_VALUES: &'static [Self] = &[ Self::hlint, Self::hluint, @@ -64,6 +66,7 @@ impl ParameterType { Self::hlstring, Self::hlbool, Self::hlvecbytes, + Self::hlbytechunks, ]; /// Returns the variant's name or "" if unknown. pub fn variant_name(self) -> Option<&'static str> { @@ -77,6 +80,7 @@ impl ParameterType { Self::hlstring => Some("hlstring"), Self::hlbool => Some("hlbool"), Self::hlvecbytes => Some("hlvecbytes"), + Self::hlbytechunks => Some("hlbytechunks"), _ => None, } } diff --git a/src/hyperlight_common/src/flatbuffers/hyperlight/generated/parameter_value_generated.rs b/src/hyperlight_common/src/flatbuffers/hyperlight/generated/parameter_value_generated.rs index 8113df5fc9..5ddab887fb 100644 --- a/src/hyperlight_common/src/flatbuffers/hyperlight/generated/parameter_value_generated.rs +++ b/src/hyperlight_common/src/flatbuffers/hyperlight/generated/parameter_value_generated.rs @@ -19,13 +19,13 @@ pub const ENUM_MIN_PARAMETER_VALUE: u8 = 0; since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021." )] -pub const ENUM_MAX_PARAMETER_VALUE: u8 = 9; +pub const ENUM_MAX_PARAMETER_VALUE: u8 = 11; #[deprecated( since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021." )] #[allow(non_camel_case_types)] -pub const ENUM_VALUES_PARAMETER_VALUE: [ParameterValue; 10] = [ +pub const ENUM_VALUES_PARAMETER_VALUE: [ParameterValue; 12] = [ ParameterValue::NONE, ParameterValue::hlint, ParameterValue::hluint, @@ -36,6 +36,8 @@ pub const ENUM_VALUES_PARAMETER_VALUE: [ParameterValue; 10] = [ ParameterValue::hlstring, ParameterValue::hlbool, ParameterValue::hlvecbytes, + ParameterValue::hlexternalbytes, + ParameterValue::hlbytechunks, ]; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] @@ -53,9 +55,11 @@ impl ParameterValue { pub const hlstring: Self = Self(7); pub const hlbool: Self = Self(8); pub const hlvecbytes: Self = Self(9); + pub const hlexternalbytes: Self = Self(10); + pub const hlbytechunks: Self = Self(11); pub const ENUM_MIN: u8 = 0; - pub const ENUM_MAX: u8 = 9; + pub const ENUM_MAX: u8 = 11; pub const ENUM_VALUES: &'static [Self] = &[ Self::NONE, Self::hlint, @@ -67,6 +71,8 @@ impl ParameterValue { Self::hlstring, Self::hlbool, Self::hlvecbytes, + Self::hlexternalbytes, + Self::hlbytechunks, ]; /// Returns the variant's name or "" if unknown. pub fn variant_name(self) -> Option<&'static str> { @@ -81,6 +87,8 @@ impl ParameterValue { Self::hlstring => Some("hlstring"), Self::hlbool => Some("hlbool"), Self::hlvecbytes => Some("hlvecbytes"), + Self::hlexternalbytes => Some("hlexternalbytes"), + Self::hlbytechunks => Some("hlbytechunks"), _ => None, } } diff --git a/src/hyperlight_common/src/flatbuffers/hyperlight/generated/return_type_generated.rs b/src/hyperlight_common/src/flatbuffers/hyperlight/generated/return_type_generated.rs index 913b1fe78f..06ea93d9c5 100644 --- a/src/hyperlight_common/src/flatbuffers/hyperlight/generated/return_type_generated.rs +++ b/src/hyperlight_common/src/flatbuffers/hyperlight/generated/return_type_generated.rs @@ -19,13 +19,13 @@ pub const ENUM_MIN_RETURN_TYPE: u8 = 0; since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021." )] -pub const ENUM_MAX_RETURN_TYPE: u8 = 9; +pub const ENUM_MAX_RETURN_TYPE: u8 = 10; #[deprecated( since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021." )] #[allow(non_camel_case_types)] -pub const ENUM_VALUES_RETURN_TYPE: [ReturnType; 10] = [ +pub const ENUM_VALUES_RETURN_TYPE: [ReturnType; 11] = [ ReturnType::hlint, ReturnType::hluint, ReturnType::hllong, @@ -36,6 +36,7 @@ pub const ENUM_VALUES_RETURN_TYPE: [ReturnType; 10] = [ ReturnType::hlbool, ReturnType::hlvoid, ReturnType::hlsizeprefixedbuffer, + ReturnType::hlbytechunks, ]; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] @@ -53,9 +54,10 @@ impl ReturnType { pub const hlbool: Self = Self(7); pub const hlvoid: Self = Self(8); pub const hlsizeprefixedbuffer: Self = Self(9); + pub const hlbytechunks: Self = Self(10); pub const ENUM_MIN: u8 = 0; - pub const ENUM_MAX: u8 = 9; + pub const ENUM_MAX: u8 = 10; pub const ENUM_VALUES: &'static [Self] = &[ Self::hlint, Self::hluint, @@ -67,6 +69,7 @@ impl ReturnType { Self::hlbool, Self::hlvoid, Self::hlsizeprefixedbuffer, + Self::hlbytechunks, ]; /// Returns the variant's name or "" if unknown. pub fn variant_name(self) -> Option<&'static str> { @@ -81,6 +84,7 @@ impl ReturnType { Self::hlbool => Some("hlbool"), Self::hlvoid => Some("hlvoid"), Self::hlsizeprefixedbuffer => Some("hlsizeprefixedbuffer"), + Self::hlbytechunks => Some("hlbytechunks"), _ => None, } } diff --git a/src/hyperlight_common/src/flatbuffers/hyperlight/generated/return_value_box_generated.rs b/src/hyperlight_common/src/flatbuffers/hyperlight/generated/return_value_box_generated.rs index cecd8b6c18..854879a0e2 100644 --- a/src/hyperlight_common/src/flatbuffers/hyperlight/generated/return_value_box_generated.rs +++ b/src/hyperlight_common/src/flatbuffers/hyperlight/generated/return_value_box_generated.rs @@ -212,6 +212,34 @@ impl<'a> ReturnValueBox<'a> { None } } + + #[inline] + #[allow(non_snake_case)] + pub fn value_as_hlexternalbytes(&self) -> Option> { + if self.value_type() == ReturnValue::hlexternalbytes { + let u = self.value(); + // Safety: + // Created from a valid Table for this object + // Which contains a valid union in this slot + Some(unsafe { hlexternalbytes::init_from_table(u) }) + } else { + None + } + } + + #[inline] + #[allow(non_snake_case)] + pub fn value_as_hlsizeprefixedbytechunks(&self) -> Option> { + if self.value_type() == ReturnValue::hlsizeprefixedbytechunks { + let u = self.value(); + // Safety: + // Created from a valid Table for this object + // Which contains a valid union in this slot + Some(unsafe { hlsizeprefixedbytechunks::init_from_table(u) }) + } else { + None + } + } } impl flatbuffers::Verifiable for ReturnValueBox<'_> { @@ -222,67 +250,24 @@ impl flatbuffers::Verifiable for ReturnValueBox<'_> { ) -> Result<(), flatbuffers::InvalidFlatbuffer> { use self::flatbuffers::Verifiable; v.visit_table(pos)? - .visit_union::( - "value_type", - Self::VT_VALUE_TYPE, - "value", - Self::VT_VALUE, - true, - |key, v, pos| match key { - ReturnValue::hlint => v - .verify_union_variant::>( - "ReturnValue::hlint", - pos, - ), - ReturnValue::hluint => v - .verify_union_variant::>( - "ReturnValue::hluint", - pos, - ), - ReturnValue::hllong => v - .verify_union_variant::>( - "ReturnValue::hllong", - pos, - ), - ReturnValue::hlulong => v - .verify_union_variant::>( - "ReturnValue::hlulong", - pos, - ), - ReturnValue::hlfloat => v - .verify_union_variant::>( - "ReturnValue::hlfloat", - pos, - ), - ReturnValue::hldouble => v - .verify_union_variant::>( - "ReturnValue::hldouble", - pos, - ), - ReturnValue::hlstring => v - .verify_union_variant::>( - "ReturnValue::hlstring", - pos, - ), - ReturnValue::hlbool => v - .verify_union_variant::>( - "ReturnValue::hlbool", - pos, - ), - ReturnValue::hlvoid => v - .verify_union_variant::>( - "ReturnValue::hlvoid", - pos, - ), - ReturnValue::hlsizeprefixedbuffer => v - .verify_union_variant::>( - "ReturnValue::hlsizeprefixedbuffer", - pos, - ), - _ => Ok(()), - }, - )? - .finish(); + .visit_union::("value_type", Self::VT_VALUE_TYPE, "value", Self::VT_VALUE, true, |key, v, pos| { + match key { + ReturnValue::hlint => v.verify_union_variant::>("ReturnValue::hlint", pos), + ReturnValue::hluint => v.verify_union_variant::>("ReturnValue::hluint", pos), + ReturnValue::hllong => v.verify_union_variant::>("ReturnValue::hllong", pos), + ReturnValue::hlulong => v.verify_union_variant::>("ReturnValue::hlulong", pos), + ReturnValue::hlfloat => v.verify_union_variant::>("ReturnValue::hlfloat", pos), + ReturnValue::hldouble => v.verify_union_variant::>("ReturnValue::hldouble", pos), + ReturnValue::hlstring => v.verify_union_variant::>("ReturnValue::hlstring", pos), + ReturnValue::hlbool => v.verify_union_variant::>("ReturnValue::hlbool", pos), + ReturnValue::hlvoid => v.verify_union_variant::>("ReturnValue::hlvoid", pos), + ReturnValue::hlsizeprefixedbuffer => v.verify_union_variant::>("ReturnValue::hlsizeprefixedbuffer", pos), + ReturnValue::hlexternalbytes => v.verify_union_variant::>("ReturnValue::hlexternalbytes", pos), + ReturnValue::hlsizeprefixedbytechunks => v.verify_union_variant::>("ReturnValue::hlsizeprefixedbytechunks", pos), + _ => Ok(()), + } + })? + .finish(); Ok(()) } } @@ -441,6 +426,26 @@ impl core::fmt::Debug for ReturnValueBox<'_> { ) } } + ReturnValue::hlexternalbytes => { + if let Some(x) = self.value_as_hlexternalbytes() { + ds.field("value", &x) + } else { + ds.field( + "value", + &"InvalidFlatbuffer: Union discriminant does not match value.", + ) + } + } + ReturnValue::hlsizeprefixedbytechunks => { + if let Some(x) = self.value_as_hlsizeprefixedbytechunks() { + ds.field("value", &x) + } else { + ds.field( + "value", + &"InvalidFlatbuffer: Union discriminant does not match value.", + ) + } + } _ => { let x: Option<()> = None; ds.field("value", &x) diff --git a/src/hyperlight_common/src/flatbuffers/hyperlight/generated/return_value_generated.rs b/src/hyperlight_common/src/flatbuffers/hyperlight/generated/return_value_generated.rs index d13c736236..6a6e619f66 100644 --- a/src/hyperlight_common/src/flatbuffers/hyperlight/generated/return_value_generated.rs +++ b/src/hyperlight_common/src/flatbuffers/hyperlight/generated/return_value_generated.rs @@ -19,13 +19,13 @@ pub const ENUM_MIN_RETURN_VALUE: u8 = 0; since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021." )] -pub const ENUM_MAX_RETURN_VALUE: u8 = 10; +pub const ENUM_MAX_RETURN_VALUE: u8 = 12; #[deprecated( since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021." )] #[allow(non_camel_case_types)] -pub const ENUM_VALUES_RETURN_VALUE: [ReturnValue; 11] = [ +pub const ENUM_VALUES_RETURN_VALUE: [ReturnValue; 13] = [ ReturnValue::NONE, ReturnValue::hlint, ReturnValue::hluint, @@ -37,6 +37,8 @@ pub const ENUM_VALUES_RETURN_VALUE: [ReturnValue; 11] = [ ReturnValue::hlbool, ReturnValue::hlvoid, ReturnValue::hlsizeprefixedbuffer, + ReturnValue::hlexternalbytes, + ReturnValue::hlsizeprefixedbytechunks, ]; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] @@ -55,9 +57,11 @@ impl ReturnValue { pub const hlbool: Self = Self(8); pub const hlvoid: Self = Self(9); pub const hlsizeprefixedbuffer: Self = Self(10); + pub const hlexternalbytes: Self = Self(11); + pub const hlsizeprefixedbytechunks: Self = Self(12); pub const ENUM_MIN: u8 = 0; - pub const ENUM_MAX: u8 = 10; + pub const ENUM_MAX: u8 = 12; pub const ENUM_VALUES: &'static [Self] = &[ Self::NONE, Self::hlint, @@ -70,6 +74,8 @@ impl ReturnValue { Self::hlbool, Self::hlvoid, Self::hlsizeprefixedbuffer, + Self::hlexternalbytes, + Self::hlsizeprefixedbytechunks, ]; /// Returns the variant's name or "" if unknown. pub fn variant_name(self) -> Option<&'static str> { @@ -85,6 +91,8 @@ impl ReturnValue { Self::hlbool => Some("hlbool"), Self::hlvoid => Some("hlvoid"), Self::hlsizeprefixedbuffer => Some("hlsizeprefixedbuffer"), + Self::hlexternalbytes => Some("hlexternalbytes"), + Self::hlsizeprefixedbytechunks => Some("hlsizeprefixedbytechunks"), _ => None, } } diff --git a/src/hyperlight_common/src/flatbuffers/mod.rs b/src/hyperlight_common/src/flatbuffers/mod.rs index 1842605720..6e8f1125b6 100644 --- a/src/hyperlight_common/src/flatbuffers/mod.rs +++ b/src/hyperlight_common/src/flatbuffers/mod.rs @@ -40,8 +40,14 @@ pub mod hyperlight { pub use self::hlbool_generated::*; mod hlvecbytes_generated; pub use self::hlvecbytes_generated::*; + mod hlbytechunks_generated; + pub use self::hlbytechunks_generated::*; + mod hlexternalbytes_generated; + pub use self::hlexternalbytes_generated::*; mod hlsizeprefixedbuffer_generated; pub use self::hlsizeprefixedbuffer_generated::*; + mod hlsizeprefixedbytechunks_generated; + pub use self::hlsizeprefixedbytechunks_generated::*; mod hlvoid_generated; pub use self::hlvoid_generated::*; mod guest_error_generated; diff --git a/src/hyperlight_common/src/func/mod.rs b/src/hyperlight_common/src/func/mod.rs index ace751bd86..5942162b78 100644 --- a/src/hyperlight_common/src/func/mod.rs +++ b/src/hyperlight_common/src/func/mod.rs @@ -26,6 +26,8 @@ pub use functions::Function; pub use param_type::{ParameterTuple, SupportedParameterType}; pub use ret_type::{ResultType, SupportedReturnType}; +/// Re-export for chunk-preserving byte values +pub use crate::flatbuffer_wrappers::function_types::Bytes; /// Re-export for `ParameterValue` enum pub use crate::flatbuffer_wrappers::function_types::ParameterValue; /// Re-export for `ReturnType` enum diff --git a/src/hyperlight_common/src/func/param_type.rs b/src/hyperlight_common/src/func/param_type.rs index 8867345b37..6fda134855 100644 --- a/src/hyperlight_common/src/func/param_type.rs +++ b/src/hyperlight_common/src/func/param_type.rs @@ -7,7 +7,7 @@ use alloc::vec::Vec; use super::error::Error; use super::utils::for_each_tuple; -use crate::flatbuffer_wrappers::function_types::{ParameterType, ParameterValue}; +use crate::flatbuffer_wrappers::function_types::{Bytes, ParameterType, ParameterValue}; /// This is a marker trait that is used to indicate that a type is a /// valid Hyperlight parameter type. @@ -37,6 +37,7 @@ macro_rules! for_each_param_type { $macro!(f64, Double); $macro!(bool, Bool); $macro!(Vec, VecBytes); + $macro!(Vec, ByteChunks); }; } @@ -122,3 +123,22 @@ macro_rules! impl_param_tuple { } for_each_tuple!(impl_param_tuple); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn byte_chunks_parameter_round_trips_without_copying() { + let chunks = vec![Bytes::from_static(b"hello"), Bytes::from_static(b" world")]; + let first_chunk = chunks[0].as_ptr(); + let value = as SupportedParameterType>::into_value(chunks); + let chunks = as SupportedParameterType>::from_value(value).unwrap(); + + assert_eq!(chunks[0].as_ptr(), first_chunk); + assert_eq!( + chunks, + [Bytes::from_static(b"hello"), Bytes::from_static(b" world")] + ); + } +} diff --git a/src/hyperlight_common/src/func/ret_type.rs b/src/hyperlight_common/src/func/ret_type.rs index 59a337ff22..98d36068c8 100644 --- a/src/hyperlight_common/src/func/ret_type.rs +++ b/src/hyperlight_common/src/func/ret_type.rs @@ -8,6 +8,12 @@ use super::error::Error; use crate::flatbuffer_wrappers::function_types::{ReturnType, ReturnValue}; /// This is a marker trait that is used to indicate that a type is a valid Hyperlight return type. +/// +/// `Vec` and `Vec` both represent one logical byte sequence. +/// `Vec` stores it contiguously. `Vec` represents it as chunks and +/// may avoid flattening them into one allocation. Chunk boundaries are a +/// storage detail and may change during transport. They do not delimit messages +/// or values. pub trait SupportedReturnType: Sized + Clone + Send + Sync + 'static { /// The return type of the supported return value const TYPE: ReturnType; @@ -33,6 +39,7 @@ macro_rules! for_each_return_type { $macro!(f64, Double); $macro!(bool, Bool); $macro!(Vec, VecBytes); + $macro!(Vec<$crate::func::Bytes>, ByteChunks); }; } @@ -92,3 +99,23 @@ where } for_each_return_type!(impl_supported_return_type); + +#[cfg(test)] +mod tests { + use super::*; + use crate::flatbuffer_wrappers::function_types::Bytes; + + #[test] + fn byte_chunks_return_round_trips_without_copying() { + let chunks = vec![Bytes::from_static(b"hello"), Bytes::from_static(b" world")]; + let first_chunk = chunks[0].as_ptr(); + let value = as SupportedReturnType>::into_value(chunks); + let chunks = as SupportedReturnType>::from_value(value).unwrap(); + + assert_eq!(chunks[0].as_ptr(), first_chunk); + assert_eq!( + chunks, + [Bytes::from_static(b"hello"), Bytes::from_static(b" world")] + ); + } +} diff --git a/src/hyperlight_component_util/src/hl.rs b/src/hyperlight_component_util/src/hl.rs index f2e17073e9..6029468fec 100644 --- a/src/hyperlight_component_util/src/hl.rs +++ b/src/hyperlight_component_util/src/hl.rs @@ -753,7 +753,7 @@ pub fn emit_hl_marshal_param(s: &mut State, id: Ident, pt: &Value) -> TokenStrea /// are no names in it (a unit type) pub fn emit_hl_marshal_result(s: &mut State, id: Ident, rt: &etypes::Result) -> TokenStream { match rt { - None => quote! { ::alloc::vec::Vec::new() }, + None => quote! { ::alloc::vec::Vec::::new() }, Some(vt) => { let toks = emit_hl_marshal_value(s, id, vt); quote! { { #toks } } diff --git a/src/hyperlight_guest_bin/src/guest_function/definition.rs b/src/hyperlight_guest_bin/src/guest_function/definition.rs index ae1b624d0e..82e523a593 100644 --- a/src/hyperlight_guest_bin/src/guest_function/definition.rs +++ b/src/hyperlight_guest_bin/src/guest_function/definition.rs @@ -77,6 +77,7 @@ fn into_flatbuffer_result(value: ReturnValue) -> Vec { ReturnValue::Bool(b) => get_flatbuffer_result(b), ReturnValue::String(s) => get_flatbuffer_result(s.as_str()), ReturnValue::VecBytes(v) => get_flatbuffer_result(v.as_slice()), + ReturnValue::ByteChunks(v) => get_flatbuffer_result(v), } } diff --git a/src/hyperlight_guest_capi/README.md b/src/hyperlight_guest_capi/README.md index 418b4ac616..d8c2c74bc8 100644 --- a/src/hyperlight_guest_capi/README.md +++ b/src/hyperlight_guest_capi/README.md @@ -2,13 +2,27 @@ This is a c-api wrapper over the hyperlight-guest/hyperlight-guest-bin crate. Th For examples on how to use it, see the c [simpleguest](../tests/c_guests/c_simpleguest/). -# Important +## Byte chunks -All guest functions must return a `hl_Vec*` obtained by calling one of the `hl_flatbuffer_result_from_*` functions. These functions will return a flatbuffer encoded byte-buffer of given value, for example `hl_flatbuffer_result_from_int(int)` will return the flatbuffer representation of the given int. +`ByteChunks` parameters use an array of borrowed pointer and length spans: -## NOTE +```c +hl_ByteChunks value = call->parameters[0].value.ByteChunks; +for (uintptr_t i = 0; i < value.count; i++) { + consume(value.chunks[i].data, value.chunks[i].len); +} +``` + +The parameter view is valid until the guest function returns. A value from +`hl_get_host_return_value_as_ByteChunks` remains valid until +`hl_free_byte_chunks`. `hl_result_from_ByteChunks` copies the supplied spans. -**You may not construct and return your own `hl_Vec*`**, as the hyperlight api assumes that all returned `hl_Vec*` are constructed through calls to a `hl_flatbuffer_result_from_*` function. +# Important + +Guest function wrappers return an `hl_ReturnValue*` created by an +`hl_result_from_*` function. -Additionally, note that type `hl_Vec*` is used in two different contexts. First, `hl_Vec*` is used input-parameter-type for guest functions that take a buffer of bytes. This buffer of bytes can contain **arbitrary** bytes. Second, all guest functions return a `hl_Vec*` (it might be hidden away by c macros). These `hl_Vec*` are flatbuffer-encoded data, and are not arbitrary. +## NOTE +The `hl_result_from_*` constructors establish matching tags, union payloads, +and ownership. diff --git a/src/hyperlight_guest_capi/cbindgen.toml b/src/hyperlight_guest_capi/cbindgen.toml index 89a4b93de3..67866a5242 100644 --- a/src/hyperlight_guest_capi/cbindgen.toml +++ b/src/hyperlight_guest_capi/cbindgen.toml @@ -20,8 +20,11 @@ prefix_with_name = true prefix = "hl_" [export.rename] +"FfiByteChunk" = "ByteChunk" +"FfiByteChunks" = "ByteChunks" "FfiFunctionCall" = "FunctionCall" "FfiParameter" = "Parameter" "FfiParameterValue" = "ParameterValue" +"FfiReturnValue" = "ReturnValue" +"FfiReturnValueUnion" = "ReturnValueUnion" "FfiVec" = "Vec" - diff --git a/src/hyperlight_guest_capi/include/macro.h b/src/hyperlight_guest_capi/include/macro.h index 1c6dc1ff7c..9e49ab5499 100644 --- a/src/hyperlight_guest_capi/include/macro.h +++ b/src/hyperlight_guest_capi/include/macro.h @@ -8,9 +8,9 @@ // // Parameters: 1. A function name // 2. The return type of the function. This must be one of the variant names in hl_ReturnType -// Note: This macro does not work for functions that return VecBytes. Instead, +// Note: This macro does not work for functions that return VecBytes or ByteChunks. Instead, // use `hl_register_function_definition` directly. You'll also need to return -// a flatbuffer-encoded hl_Vec* using the various hl_flatbuffer_result_from_* functions. +// an hl_ReturnValue* using the hl_result_from_* functions. // See c_simpleguest/main.c for an example. // 3. The number of parameters the function takes // 4+ The types of the parameters the function takes. The must be one of the variant names @@ -18,9 +18,9 @@ #define HYPERLIGHT_WRAP_FUNCTION(function, return_type, paramsc, ... ) HYPERLIGHT_WRAP_FUNCTION_##paramsc(function, return_type, __VA_ARGS__) #define HYPERLIGHT_WRAP_FUNCTION_0(function, return_type, ...) \ -hl_Vec *_call_##function(const hl_FunctionCall *function_call) \ +hl_ReturnValue *_call_##function(const hl_FunctionCall *function_call) \ { \ - return hl_flatbuffer_result_from_##return_type( function() \ + return hl_result_from_##return_type( function() \ ); \ } \ uintptr_t _##function##_parameter_count = 0; \ @@ -29,9 +29,9 @@ hl_ParameterType _##function##_parameter_types[] = { 0 }; \ #define HYPERLIGHT_WRAP_FUNCTION_1(function, return_type, arg1) \ -hl_Vec *_call_##function(const hl_FunctionCall *function_call) \ +hl_ReturnValue *_call_##function(const hl_FunctionCall *function_call) \ { \ - return hl_flatbuffer_result_from_##return_type( function( \ + return hl_result_from_##return_type( function( \ function_call->parameters[0].value.arg1 \ )); \ } \ @@ -40,9 +40,9 @@ hl_ReturnType _##function##_return_type = hl_ReturnType_##return_type; \ hl_ParameterType _##function##_parameter_types[] = { hl_ParameterType_##arg1 }; \ #define HYPERLIGHT_WRAP_FUNCTION_2(function, return_type, arg1, arg2) \ -hl_Vec *_call_##function(const hl_FunctionCall *function_call) \ +hl_ReturnValue *_call_##function(const hl_FunctionCall *function_call) \ { \ - return hl_flatbuffer_result_from_##return_type( function( \ + return hl_result_from_##return_type( function( \ function_call->parameters[0].value.arg1, \ function_call->parameters[1].value.arg2 \ )); \ @@ -55,9 +55,9 @@ hl_ParameterType _##function##_parameter_types[] = { hl_ParameterType_##arg1, \ #define HYPERLIGHT_WRAP_FUNCTION_3(function, return_type, arg1, arg2, arg3) \ -hl_Vec *_call_##function(const hl_FunctionCall *function_call) \ +hl_ReturnValue *_call_##function(const hl_FunctionCall *function_call) \ { \ - return hl_flatbuffer_result_from_##return_type( function( \ + return hl_result_from_##return_type( function( \ function_call->parameters[0].value.arg1, \ function_call->parameters[1].value.arg2, \ function_call->parameters[2].value.arg3 \ @@ -72,9 +72,9 @@ hl_ParameterType _##function##_parameter_types[] = { hl_ParameterType_##arg1, \ #define HYPERLIGHT_WRAP_FUNCTION_4(function, return_type, arg1, arg2, arg3, arg4) \ -hl_Vec *_call_##function(const hl_FunctionCall *function_call) \ +hl_ReturnValue *_call_##function(const hl_FunctionCall *function_call) \ { \ - return hl_flatbuffer_result_from_##return_type( function( \ + return hl_result_from_##return_type( function( \ function_call->parameters[0].value.arg1, \ function_call->parameters[1].value.arg2, \ function_call->parameters[2].value.arg3, \ @@ -90,9 +90,9 @@ hl_ParameterType _##function##_parameter_types[] = { hl_ParameterType_##arg1, \ }; \ #define HYPERLIGHT_WRAP_FUNCTION_5(function, return_type, arg1, arg2, arg3, arg4, arg5) \ -hl_Vec *_call_##function(const hl_FunctionCall *function_call) \ +hl_ReturnValue *_call_##function(const hl_FunctionCall *function_call) \ { \ - return hl_flatbuffer_result_from_##return_type( function( \ + return hl_result_from_##return_type( function( \ function_call->parameters[0].value.arg1, \ function_call->parameters[1].value.arg2, \ function_call->parameters[2].value.arg3, \ @@ -110,9 +110,9 @@ hl_ParameterType _##function##_parameter_types[] = { hl_ParameterType_##arg1, \ }; \ #define HYPERLIGHT_WRAP_FUNCTION_6(function, return_type, arg1, arg2, arg3, arg4, arg5, arg6) \ -hl_Vec *_call_##function(const hl_FunctionCall *function_call) \ +hl_ReturnValue *_call_##function(const hl_FunctionCall *function_call) \ { \ - return hl_flatbuffer_result_from_##return_type( function( \ + return hl_result_from_##return_type( function( \ function_call->parameters[0].value.arg1, \ function_call->parameters[1].value.arg2, \ function_call->parameters[2].value.arg3, \ @@ -132,9 +132,9 @@ hl_ParameterType _##function##_parameter_types[] = { hl_ParameterType_##arg1, \ }; \ #define HYPERLIGHT_WRAP_FUNCTION_7(function, return_type, arg1, arg2, arg3, arg4, arg5, arg6, arg7) \ -hl_Vec *_call_##function(const hl_FunctionCall *function_call) \ +hl_ReturnValue *_call_##function(const hl_FunctionCall *function_call) \ { \ - return hl_flatbuffer_result_from_##return_type( function( \ + return hl_result_from_##return_type( function( \ function_call->parameters[0].value.arg1, \ function_call->parameters[1].value.arg2, \ function_call->parameters[2].value.arg3, \ @@ -156,9 +156,9 @@ hl_ParameterType _##function##_parameter_types[] = { hl_ParameterType_##arg1, \ }; \ #define HYPERLIGHT_WRAP_FUNCTION_8(function, return_type, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) \ -hl_Vec *_call_##function(const hl_FunctionCall *function_call) \ +hl_ReturnValue *_call_##function(const hl_FunctionCall *function_call) \ { \ - return hl_flatbuffer_result_from_##return_type( function( \ + return hl_result_from_##return_type( function( \ function_call->parameters[0].value.arg1, \ function_call->parameters[1].value.arg2, \ function_call->parameters[2].value.arg3, \ @@ -182,9 +182,9 @@ hl_ParameterType _##function##_parameter_types[] = { hl_ParameterType_##arg1, \ }; \ #define HYPERLIGHT_WRAP_FUNCTION_9(function, return_type, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) \ -hl_Vec *_call_##function(const hl_FunctionCall *function_call) \ +hl_ReturnValue *_call_##function(const hl_FunctionCall *function_call) \ { \ - return hl_flatbuffer_result_from_##return_type( function( \ + return hl_result_from_##return_type( function( \ function_call->parameters[0].value.arg1, \ function_call->parameters[1].value.arg2, \ function_call->parameters[2].value.arg3, \ @@ -210,9 +210,9 @@ hl_ParameterType _##function##_parameter_types[] = { hl_ParameterType_##arg1, \ }; \ #define HYPERLIGHT_WRAP_FUNCTION_10(function, return_type, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) \ -hl_Vec *_call_##function(const hl_FunctionCall *function_call) \ +hl_ReturnValue *_call_##function(const hl_FunctionCall *function_call) \ { \ - return hl_flatbuffer_result_from_##return_type( function( \ + return hl_result_from_##return_type( function( \ function_call->parameters[0].value.arg1, \ function_call->parameters[1].value.arg2, \ function_call->parameters[2].value.arg3, \ @@ -240,9 +240,9 @@ hl_ParameterType _##function##_parameter_types[] = { hl_ParameterType_##arg1, \ }; \ #define HYPERLIGHT_WRAP_FUNCTION_11(function, return_type, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11) \ -hl_Vec *_call_##function(const hl_FunctionCall *function_call) \ +hl_ReturnValue *_call_##function(const hl_FunctionCall *function_call) \ { \ - return hl_flatbuffer_result_from_##return_type( function( \ + return hl_result_from_##return_type( function( \ function_call->parameters[0].value.arg1, \ function_call->parameters[1].value.arg2, \ function_call->parameters[2].value.arg3, \ diff --git a/src/hyperlight_guest_capi/src/dispatch.rs b/src/hyperlight_guest_capi/src/dispatch.rs index aea58e4ee8..9bd3d1f013 100644 --- a/src/hyperlight_guest_capi/src/dispatch.rs +++ b/src/hyperlight_guest_capi/src/dispatch.rs @@ -7,23 +7,43 @@ use alloc::vec::Vec; use core::ffi::{CStr, c_char}; use hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall; -use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterType, ReturnType}; +use hyperlight_common::flatbuffer_wrappers::function_types::{ + ParameterType, ReturnType, ReturnValue, +}; use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; +use hyperlight_common::flatbuffer_wrappers::util::get_flatbuffer_result; use hyperlight_guest::error::{HyperlightGuestError, Result}; use hyperlight_guest_bin::guest_function::definition::GuestFunctionDefinition; use hyperlight_guest_bin::guest_function::register::GuestFunctionRegister; -use hyperlight_guest_bin::host_comm::call_host_function_without_returning_result; +use hyperlight_guest_bin::host_comm::{ + call_host_function_without_returning_result, get_host_return_value, +}; -use crate::types::{FfiFunctionCall, FfiVec}; +use crate::types::{FfiFunctionCall, FfiReturnValue, OwnedFfiFunctionCall}; static mut REGISTERED_C_GUEST_FUNCTIONS: GuestFunctionRegister = GuestFunctionRegister::new(); -type CGuestFunc = extern "C" fn(&FfiFunctionCall) -> Box; +type CGuestFunc = extern "C" fn(&FfiFunctionCall) -> *mut FfiReturnValue; unsafe extern "C" { - // NOTE *mut FfiVec must be a Box. This will be the case as long as the guest - // returns a FfiVec that they created using the c-api hl_flatbuffer_result_from_* functions. - fn c_guest_dispatch_function(function_call: &FfiFunctionCall) -> *mut FfiVec; + // The guest must return a value created by an hl_result_from_* function. + fn c_guest_dispatch_function(function_call: &FfiFunctionCall) -> *mut FfiReturnValue; +} + +fn encode_return_value(value: ReturnValue) -> Vec { + match value { + ReturnValue::Int(value) => get_flatbuffer_result(value), + ReturnValue::UInt(value) => get_flatbuffer_result(value), + ReturnValue::Long(value) => get_flatbuffer_result(value), + ReturnValue::ULong(value) => get_flatbuffer_result(value), + ReturnValue::Float(value) => get_flatbuffer_result(value), + ReturnValue::Double(value) => get_flatbuffer_result(value), + ReturnValue::Bool(value) => get_flatbuffer_result(value), + ReturnValue::String(value) => get_flatbuffer_result(value.as_str()), + ReturnValue::VecBytes(value) => get_flatbuffer_result(value.as_slice()), + ReturnValue::ByteChunks(value) => get_flatbuffer_result(value), + ReturnValue::Void(()) => get_flatbuffer_result(()), + } } #[unsafe(no_mangle)] @@ -41,10 +61,22 @@ pub fn guest_dispatch_function(function_call: FunctionCall) -> Result> { .collect(); registered_func.verify_parameters(&function_call_parameter_types)?; - let ffi_func_call = FfiFunctionCall::from_function_call(function_call)?; - let function_result = (registered_func.function_pointer)(&ffi_func_call); + let function_name = function_call.function_name.clone(); + let ffi_func_call = OwnedFfiFunctionCall::from_function_call(function_call)?; + let function_result = (registered_func.function_pointer)(ffi_func_call.as_ffi()); + if function_result.is_null() { + return Err(HyperlightGuestError::new( + ErrorCode::GuestError, + alloc::format!("C guest function {function_name:?} returned null"), + )); + } + + // SAFETY: the pointer is non-null and C functions return ownership. + let function_result = unsafe { Box::from_raw(function_result) }; + // SAFETY: registered C functions return values created by hl_result_from_*. + let function_result = unsafe { (*function_result).into_return_value() }; - unsafe { Ok(FfiVec::into_vec(*function_result)) } + Ok(encode_return_value(function_result)) } else { // The given function is not registered. The guest should implement a function called c_guest_dispatch_function to handle this. @@ -52,8 +84,8 @@ pub fn guest_dispatch_function(function_call: FunctionCall) -> Result> { // to implement the function but its seems that weak linkage is an unstable feature so for now its probably better // to not do that. let function_name = function_call.function_name.clone(); - let ffi_func_call = FfiFunctionCall::from_function_call(function_call)?; - let function_result = unsafe { c_guest_dispatch_function(&ffi_func_call) }; + let ffi_func_call = OwnedFfiFunctionCall::from_function_call(function_call)?; + let function_result = unsafe { c_guest_dispatch_function(ffi_func_call.as_ffi()) }; if function_result.is_null() { Err(HyperlightGuestError::new( ErrorCode::GuestFunctionNotFound, @@ -61,7 +93,10 @@ pub fn guest_dispatch_function(function_call: FunctionCall) -> Result> { )) } else { let result = unsafe { Box::from_raw(function_result) }; - Ok(unsafe { FfiVec::into_vec(*result) }) + // SAFETY: non-null fallback results are created by hl_result_from_*. + let result = unsafe { (*result).into_return_value() }; + + Ok(encode_return_value(result)) } } } @@ -85,15 +120,19 @@ pub extern "C" fn hl_register_function_definition( unsafe { (&mut *(&raw mut REGISTERED_C_GUEST_FUNCTIONS)).register(func_def) }; } -/// The caller is responsible for freeing the memory associated with given `FfiFunctionCall`. +/// Call a host function. The return value can be retrieved with +/// `hl_get_host_return_value_as_*` immediately after. #[unsafe(no_mangle)] pub extern "C" fn hl_call_host_function(function_call: &FfiFunctionCall) { let parameters = unsafe { function_call.copy_parameters() }; let func_name = unsafe { function_call.copy_function_name() }; let return_type = unsafe { function_call.copy_return_type() }; - // Use the non-generic internal implementation - // The C API will then call specific getter functions to fetch the properly typed return value - let _ = call_host_function_without_returning_result(&func_name, Some(parameters), return_type) + call_host_function_without_returning_result(&func_name, Some(parameters), return_type) .expect("Failed to call host function"); } + +/// Retrieve the return value from the last `hl_call_host_function`. +pub(crate) fn take_last_host_return>() -> T { + get_host_return_value().expect("Unable to get host return value") +} diff --git a/src/hyperlight_guest_capi/src/flatbuffer.rs b/src/hyperlight_guest_capi/src/flatbuffer.rs deleted file mode 100644 index a8e263123a..0000000000 --- a/src/hyperlight_guest_capi/src/flatbuffer.rs +++ /dev/null @@ -1,145 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2025 The Hyperlight Authors. - -use alloc::boxed::Box; -use alloc::ffi::CString; -use alloc::string::String; -use alloc::vec::Vec; -use core::ffi::{CStr, c_char}; - -use hyperlight_common::flatbuffer_wrappers::util::get_flatbuffer_result; -use hyperlight_guest_bin::host_comm::get_host_return_value; - -use crate::types::FfiVec; - -// The reason for the capitalized type in the function names below -// is to match the names of the variants in hl_ReturnType, -// which is used in the C macros in macro.h - -#[unsafe(no_mangle)] -pub extern "C" fn hl_flatbuffer_result_from_Int(value: i32) -> Box { - let vec = get_flatbuffer_result(value); - - Box::new(unsafe { FfiVec::from_vec(vec) }) -} - -#[unsafe(no_mangle)] -pub extern "C" fn hl_flatbuffer_result_from_UInt(value: u32) -> Box { - let vec = get_flatbuffer_result(value); - - Box::new(unsafe { FfiVec::from_vec(vec) }) -} - -#[unsafe(no_mangle)] -pub extern "C" fn hl_flatbuffer_result_from_Long(value: i64) -> Box { - let vec = get_flatbuffer_result(value); - - Box::new(unsafe { FfiVec::from_vec(vec) }) -} - -#[unsafe(no_mangle)] -pub extern "C" fn hl_flatbuffer_result_from_ULong(value: u64) -> Box { - let vec = get_flatbuffer_result(value); - - Box::new(unsafe { FfiVec::from_vec(vec) }) -} - -#[unsafe(no_mangle)] -pub extern "C" fn hl_flatbuffer_result_from_Float(value: f32) -> Box { - let vec = get_flatbuffer_result(value); - - Box::new(unsafe { FfiVec::from_vec(vec) }) -} - -#[unsafe(no_mangle)] -pub extern "C" fn hl_flatbuffer_result_from_Double(value: f64) -> Box { - let vec = get_flatbuffer_result(value); - - Box::new(unsafe { FfiVec::from_vec(vec) }) -} - -#[unsafe(no_mangle)] -pub extern "C" fn hl_flatbuffer_result_from_Void() -> Box { - let vec = get_flatbuffer_result(()); - - Box::new(unsafe { FfiVec::from_vec(vec) }) -} - -#[unsafe(no_mangle)] -pub extern "C" fn hl_flatbuffer_result_from_String(value: *const c_char) -> Box { - let str = unsafe { CStr::from_ptr(value) }; - let vec = get_flatbuffer_result(str.to_string_lossy().as_ref()); - - Box::new(unsafe { FfiVec::from_vec(vec) }) -} - -#[unsafe(no_mangle)] -pub extern "C" fn hl_flatbuffer_result_from_Bytes(data: *const u8, len: usize) -> Box { - let slice = unsafe { core::slice::from_raw_parts(data, len) }; - - let vec = get_flatbuffer_result(slice); - - Box::new(unsafe { FfiVec::from_vec(vec) }) -} - -#[unsafe(no_mangle)] -pub extern "C" fn hl_flatbuffer_result_from_Bool(value: bool) -> Box { - let vec = get_flatbuffer_result(value); - - Box::new(unsafe { FfiVec::from_vec(vec) }) -} - -//--- Functions for getting values returned by host functions calls - -#[unsafe(no_mangle)] -pub extern "C" fn hl_get_host_return_value_as_Int() -> i32 { - get_host_return_value().expect("Unable to get host return value as int") -} - -#[unsafe(no_mangle)] -pub extern "C" fn hl_get_host_return_value_as_UInt() -> u32 { - get_host_return_value().expect("Unable to get host return value as uint") -} - -// the same for long, ulong -#[unsafe(no_mangle)] -pub extern "C" fn hl_get_host_return_value_as_Long() -> i64 { - get_host_return_value().expect("Unable to get host return value as long") -} - -#[unsafe(no_mangle)] -pub extern "C" fn hl_get_host_return_value_as_ULong() -> u64 { - get_host_return_value().expect("Unable to get host return value as ulong") -} - -#[unsafe(no_mangle)] -pub extern "C" fn hl_get_host_return_value_as_Bool() -> bool { - get_host_return_value().expect("Unable to get host return value as bool") -} - -#[unsafe(no_mangle)] -pub extern "C" fn hl_get_host_return_value_as_Float() -> f32 { - get_host_return_value().expect("Unable to get host return value as f32") -} - -#[unsafe(no_mangle)] -pub extern "C" fn hl_get_host_return_value_as_Double() -> f64 { - get_host_return_value().expect("Unable to get host return value as f64") -} - -#[unsafe(no_mangle)] -pub extern "C" fn hl_get_host_return_value_as_String() -> *const c_char { - let string_value: String = - get_host_return_value().expect("Unable to get host return value as string"); - - let c_string = CString::new(string_value).expect("Failed to create CString"); - c_string.into_raw() -} - -#[unsafe(no_mangle)] -pub extern "C" fn hl_get_host_return_value_as_VecBytes() -> Box { - let vec_value: Vec = - get_host_return_value().expect("Unable to get host return value as vec bytes"); - - Box::new(unsafe { FfiVec::from_vec(vec_value) }) -} diff --git a/src/hyperlight_guest_capi/src/lib.rs b/src/hyperlight_guest_capi/src/lib.rs index 0a048ba7ec..f1cb28f3ec 100644 --- a/src/hyperlight_guest_capi/src/lib.rs +++ b/src/hyperlight_guest_capi/src/lib.rs @@ -8,6 +8,6 @@ extern crate alloc; pub mod dispatch; pub mod error; -pub mod flatbuffer; pub mod logging; +pub mod return_value; pub mod types; diff --git a/src/hyperlight_guest_capi/src/return_value.rs b/src/hyperlight_guest_capi/src/return_value.rs new file mode 100644 index 0000000000..d736ac193d --- /dev/null +++ b/src/hyperlight_guest_capi/src/return_value.rs @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 The Hyperlight Authors. + +use alloc::boxed::Box; +use alloc::ffi::CString; +use alloc::string::String; +use alloc::vec::Vec; +use core::ffi::{CStr, c_char}; + +use hyperlight_common::flatbuffer_wrappers::function_types::Bytes; + +use crate::dispatch::take_last_host_return; +use crate::types::{FfiByteChunks, FfiReturnValue, FfiVec, OwnedFfiByteChunks}; + +// The reason for the capitalized type in the function names below +// is to match the names of the variants in hl_ReturnType, +// which is used in the C macros in macro.h + +#[unsafe(no_mangle)] +pub extern "C" fn hl_result_from_Int(value: i32) -> Box { + Box::new(FfiReturnValue::int(value)) +} + +#[unsafe(no_mangle)] +pub extern "C" fn hl_result_from_UInt(value: u32) -> Box { + Box::new(FfiReturnValue::uint(value)) +} + +#[unsafe(no_mangle)] +pub extern "C" fn hl_result_from_Long(value: i64) -> Box { + Box::new(FfiReturnValue::long(value)) +} + +#[unsafe(no_mangle)] +pub extern "C" fn hl_result_from_ULong(value: u64) -> Box { + Box::new(FfiReturnValue::ulong(value)) +} + +#[unsafe(no_mangle)] +pub extern "C" fn hl_result_from_Float(value: f32) -> Box { + Box::new(FfiReturnValue::float(value)) +} + +#[unsafe(no_mangle)] +pub extern "C" fn hl_result_from_Double(value: f64) -> Box { + Box::new(FfiReturnValue::double(value)) +} + +#[unsafe(no_mangle)] +pub extern "C" fn hl_result_from_Void() -> Box { + Box::new(FfiReturnValue::void()) +} + +#[unsafe(no_mangle)] +/// # Safety +/// +/// `value` must point to a live NUL-terminated string. +pub unsafe extern "C" fn hl_result_from_String(value: *const c_char) -> Box { + // SAFETY: callers provide a live NUL-terminated string. + let value = unsafe { CStr::from_ptr(value) }; + Box::new(FfiReturnValue::string(value)) +} + +#[unsafe(no_mangle)] +/// # Safety +/// +/// `data` must reference `len` readable bytes when `len` is nonzero. +pub unsafe extern "C" fn hl_result_from_Bytes(data: *const u8, len: usize) -> Box { + let value = if len == 0 { + Vec::new() + } else { + // SAFETY: callers provide `len` readable bytes. + unsafe { core::slice::from_raw_parts(data, len) }.to_vec() + }; + Box::new(FfiReturnValue::vec_bytes(value)) +} + +#[unsafe(no_mangle)] +/// # Safety +/// +/// Every pointer in `value` must reference its declared number of bytes. +pub unsafe extern "C" fn hl_result_from_ByteChunks(value: FfiByteChunks) -> Box { + // SAFETY: required by the caller. + Box::new(unsafe { FfiReturnValue::byte_chunks(value) }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn hl_result_from_Bool(value: bool) -> Box { + Box::new(FfiReturnValue::boolean(value)) +} + +//--- Functions for getting values returned by host functions calls + +#[unsafe(no_mangle)] +pub extern "C" fn hl_get_host_return_value_as_Int() -> i32 { + take_last_host_return() +} + +#[unsafe(no_mangle)] +pub extern "C" fn hl_get_host_return_value_as_UInt() -> u32 { + take_last_host_return() +} + +// the same for long, ulong +#[unsafe(no_mangle)] +pub extern "C" fn hl_get_host_return_value_as_Long() -> i64 { + take_last_host_return() +} + +#[unsafe(no_mangle)] +pub extern "C" fn hl_get_host_return_value_as_ULong() -> u64 { + take_last_host_return() +} + +#[unsafe(no_mangle)] +pub extern "C" fn hl_get_host_return_value_as_Bool() -> bool { + take_last_host_return() +} + +#[unsafe(no_mangle)] +pub extern "C" fn hl_get_host_return_value_as_Float() -> f32 { + take_last_host_return() +} + +#[unsafe(no_mangle)] +pub extern "C" fn hl_get_host_return_value_as_Double() -> f64 { + take_last_host_return() +} + +#[unsafe(no_mangle)] +pub extern "C" fn hl_get_host_return_value_as_String() -> *const c_char { + let string_value: String = take_last_host_return(); + + let c_string = CString::new(string_value).expect("Failed to create CString"); + c_string.into_raw() +} + +#[unsafe(no_mangle)] +pub extern "C" fn hl_get_host_return_value_as_VecBytes() -> Box { + let vec_value: Vec = take_last_host_return(); + + Box::new(unsafe { FfiVec::from_vec(vec_value) }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn hl_get_host_return_value_as_ByteChunks() -> *mut FfiByteChunks { + let chunks: Vec = take_last_host_return(); + + OwnedFfiByteChunks::into_raw(chunks) +} + +#[unsafe(no_mangle)] +/// # Safety +/// +/// `value` must be null or a pointer returned by +/// [`hl_get_host_return_value_as_ByteChunks`] that has not already been freed. +pub unsafe extern "C" fn hl_free_byte_chunks(value: *mut FfiByteChunks) { + // SAFETY: required by the caller. + unsafe { OwnedFfiByteChunks::free(value) }; +} diff --git a/src/hyperlight_guest_capi/src/types.rs b/src/hyperlight_guest_capi/src/types.rs index 85c7183c55..dd2200b151 100644 --- a/src/hyperlight_guest_capi/src/types.rs +++ b/src/hyperlight_guest_capi/src/types.rs @@ -1,11 +1,17 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2025 The Hyperlight Authors. +mod byte_chunks; +pub use byte_chunks::*; + mod function_call; pub use function_call::*; mod parameter; pub use parameter::*; +mod return_value; +pub use return_value::*; + mod vec; pub use vec::*; diff --git a/src/hyperlight_guest_capi/src/types/byte_chunks.rs b/src/hyperlight_guest_capi/src/types/byte_chunks.rs new file mode 100644 index 0000000000..19e36668db --- /dev/null +++ b/src/hyperlight_guest_capi/src/types/byte_chunks.rs @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2026 The Hyperlight Authors. + +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::{ptr, slice}; + +use hyperlight_common::flatbuffer_wrappers::function_types::Bytes; + +/// One borrowed byte chunk exposed through the C API. +#[repr(C)] +#[derive(Copy, Clone)] +pub struct FfiByteChunk { + data: *const u8, + len: usize, +} + +impl FfiByteChunk { + #[cfg(test)] + pub(crate) fn data(&self) -> *const u8 { + self.data + } + + fn borrowed(value: &Bytes) -> Self { + Self { + data: value.as_ptr(), + len: value.len(), + } + } + + fn from_owned_vec(value: Vec) -> Self { + let value = value.into_boxed_slice(); + let len = value.len(); + let data = Box::into_raw(value) as *mut u8; + Self { data, len } + } + + /// # Safety + /// + /// `data` must reference `len` readable bytes when `len` is nonzero. + unsafe fn as_slice(&self) -> &[u8] { + if self.len == 0 { + &[] + } else { + // SAFETY: required by the caller. + unsafe { slice::from_raw_parts(self.data, self.len) } + } + } + + /// # Safety + /// + /// This chunk must have been created by [`Self::from_owned_vec`] and must + /// not have been consumed before. + unsafe fn into_owned_bytes(self) -> Bytes { + let value = ptr::slice_from_raw_parts_mut(self.data.cast_mut(), self.len); + // SAFETY: required by the caller. + let value = unsafe { Box::from_raw(value) }; + Bytes::from(value.into_vec()) + } + + /// # Safety + /// + /// This chunk must have been created by [`Self::from_owned_vec`] and must + /// not have been consumed before. + unsafe fn drop_owned(self) { + let value = ptr::slice_from_raw_parts_mut(self.data.cast_mut(), self.len); + // SAFETY: required by the caller. + drop(unsafe { Box::from_raw(value) }); + } +} + +/// A borrowed array of byte chunks exposed through the C API. +#[repr(C)] +#[derive(Copy, Clone)] +pub struct FfiByteChunks { + chunks: *const FfiByteChunk, + count: usize, +} + +impl FfiByteChunks { + /// # Safety + /// + /// `chunks` must reference `count` live descriptors when `count` is + /// nonzero. Each descriptor must reference its declared number of bytes. + pub(crate) unsafe fn copy_to_bytes(self) -> Vec { + // SAFETY: required by the caller. + unsafe { self.as_slice() } + .iter() + .map(|chunk| { + // SAFETY: required by the caller. + Bytes::copy_from_slice(unsafe { chunk.as_slice() }) + }) + .collect() + } + + /// # Safety + /// + /// `chunks` must reference `count` live descriptors when `count` is + /// nonzero. Each descriptor must reference its declared number of bytes. + pub(crate) unsafe fn copy_owned(self) -> Self { + // Copy every input before leaking any allocation into the owned view. + // SAFETY: required by the caller. + let chunks = unsafe { self.as_slice() } + .iter() + .map(|chunk| { + // SAFETY: required by the caller. + unsafe { chunk.as_slice() }.to_vec() + }) + .collect(); + Self::from_owned_chunks(chunks) + } + + /// # Safety + /// + /// This value must have been created by [`Self::copy_owned`] and must not + /// have been consumed before. + pub(crate) unsafe fn into_owned_bytes(self) -> Vec { + // SAFETY: required by the caller. + let chunks = unsafe { self.into_owned_descriptors() }; + chunks + .into_vec() + .into_iter() + .map(|chunk| { + // SAFETY: every descriptor owns an allocation created by + // `from_owned_chunks`. + unsafe { chunk.into_owned_bytes() } + }) + .collect() + } + + /// # Safety + /// + /// This value must have been created by [`Self::copy_owned`] and must not + /// have been consumed before. + pub(crate) unsafe fn drop_owned(self) { + // SAFETY: required by the caller. + let chunks = unsafe { self.into_owned_descriptors() }; + for chunk in chunks.iter().copied() { + // SAFETY: every descriptor owns an allocation created by + // `from_owned_chunks`. + unsafe { chunk.drop_owned() }; + } + } + + /// # Safety + /// + /// `chunks` must reference `count` live descriptors when `count` is + /// nonzero. + pub(crate) unsafe fn as_slice(&self) -> &[FfiByteChunk] { + if self.count == 0 { + &[] + } else { + // SAFETY: required by the caller. + unsafe { slice::from_raw_parts(self.chunks, self.count) } + } + } + + fn from_owned_chunks(chunks: Vec>) -> Self { + let chunks: Vec<_> = chunks + .into_iter() + .map(FfiByteChunk::from_owned_vec) + .collect(); + + let chunks = chunks.into_boxed_slice(); + let count = chunks.len(); + let chunks = Box::into_raw(chunks) as *mut FfiByteChunk; + + Self { chunks, count } + } + + /// # Safety + /// + /// This value must have been created by [`Self::from_owned_chunks`] and + /// must not have been consumed before. + unsafe fn into_owned_descriptors(self) -> Box<[FfiByteChunk]> { + let chunks = ptr::slice_from_raw_parts_mut(self.chunks.cast_mut(), self.count); + // SAFETY: required by the caller. + unsafe { Box::from_raw(chunks) } + } +} + +/// Owns the Rust chunks and descriptors behind one borrowed C view. +pub(crate) struct FfiByteChunksOwner { + _chunks: Vec, + descriptors: Box<[FfiByteChunk]>, +} + +impl FfiByteChunksOwner { + pub(crate) fn new(chunks: Vec) -> Self { + let descriptors = chunks + .iter() + .map(FfiByteChunk::borrowed) + .collect::>() + .into_boxed_slice(); + Self { + _chunks: chunks, + descriptors, + } + } + + pub(crate) fn view(&self) -> FfiByteChunks { + FfiByteChunks { + chunks: self.descriptors.as_ptr(), + count: self.descriptors.len(), + } + } +} + +/// Keeps a host return alive behind the public view pointer. +#[repr(C)] +pub(crate) struct OwnedFfiByteChunks { + view: FfiByteChunks, + _owner: FfiByteChunksOwner, +} + +impl OwnedFfiByteChunks { + pub(crate) fn into_raw(chunks: Vec) -> *mut FfiByteChunks { + let owner = FfiByteChunksOwner::new(chunks); + let value = Box::new(Self { + view: owner.view(), + _owner: owner, + }); + Box::into_raw(value).cast() + } + + /// # Safety + /// + /// `value` must be null or a pointer returned by [`Self::into_raw`] that + /// has not already been freed. + pub(crate) unsafe fn free(value: *mut FfiByteChunks) { + if !value.is_null() { + // SAFETY: `view` is the first field of this `repr(C)` allocation. + drop(unsafe { Box::from_raw(value.cast::()) }); + } + } +} + +#[cfg(test)] +mod tests { + use alloc::vec; + + use super::*; + + #[test] + fn borrowed_view_preserves_chunks_without_copying() { + let chunks = vec![Bytes::from_static(b"first"), Bytes::from_static(b"second")]; + let addresses = chunks.iter().map(Bytes::as_ptr).collect::>(); + let owner = FfiByteChunksOwner::new(chunks); + let view = owner.view(); + + // SAFETY: `owner` keeps the descriptor array and chunks alive. + let descriptors = unsafe { view.as_slice() }; + assert_eq!(descriptors.len(), 2); + assert_eq!(descriptors[0].data, addresses[0]); + assert_eq!(descriptors[1].data, addresses[1]); + } + + #[test] + fn owned_view_preserves_chunk_contents() { + let source = FfiByteChunksOwner::new(vec![ + Bytes::from_static(b"first"), + Bytes::from_static(b"second"), + ]); + + // SAFETY: `source` keeps the view live while it is copied. + let owned = unsafe { source.view().copy_owned() }; + // SAFETY: `owned` has not been consumed since `copy_owned`. + let chunks = unsafe { owned.into_owned_bytes() }; + + assert_eq!( + chunks, + vec![Bytes::from_static(b"first"), Bytes::from_static(b"second")] + ); + } +} diff --git a/src/hyperlight_guest_capi/src/types/function_call.rs b/src/hyperlight_guest_capi/src/types/function_call.rs index b91a0bb986..7d8e218558 100644 --- a/src/hyperlight_guest_capi/src/types/function_call.rs +++ b/src/hyperlight_guest_capi/src/types/function_call.rs @@ -12,7 +12,7 @@ use hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall; use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnType}; use hyperlight_guest::error::Result; -use crate::types::FfiParameter; +use crate::types::{FfiParameter, OwnedFfiParameter}; /// An FFI version of `FunctionCall` #[repr(C)] @@ -23,49 +23,75 @@ pub struct FfiFunctionCall { return_type: ReturnType, } -impl FfiFunctionCall { - /// Create a new `FfiFunctionCall` by consuming a FunctionCall. - pub fn from_function_call(value: FunctionCall) -> Result { - let leaked_function_name = CString::new(value.function_name.as_str()) - .expect("Failed to convert function name to CString") - .into_raw(); +pub(crate) struct OwnedFfiFunctionCall { + ffi: FfiFunctionCall, + _function_name: CString, + _parameters: Box<[FfiParameter]>, + _parameter_owners: Vec, +} - let (parameters, parameter_len) = match value.parameters { - Some(p) => { - let parameters: Vec = p - .into_iter() - .map(|param| FfiParameter::from_parameter_value(param).unwrap()) - .collect(); - let boxed = parameters.into_boxed_slice(); - let parameters_len = boxed.len(); - let leaked_param_vec = Box::into_raw(boxed); - (leaked_param_vec as *const FfiParameter, parameters_len) - } - None => (core::ptr::null(), 0), +impl OwnedFfiFunctionCall { + pub(crate) fn from_function_call(value: FunctionCall) -> Result { + let function_name = CString::new(value.function_name.as_str()) + .expect("Failed to convert function name to CString"); + let parameter_owners = value + .parameters + .unwrap_or_default() + .into_iter() + .map(OwnedFfiParameter::from_parameter_value) + .collect::>>()?; + let parameters = parameter_owners + .iter() + .map(OwnedFfiParameter::ffi) + .collect::>() + .into_boxed_slice(); + let parameters_len = parameters.len(); + let parameters_ptr = if parameters.is_empty() { + core::ptr::null() + } else { + parameters.as_ptr() + }; + let ffi = FfiFunctionCall { + function_name: function_name.as_ptr(), + parameters: parameters_ptr, + parameters_len, + return_type: value.expected_return_type, }; Ok(Self { - function_name: leaked_function_name, - parameters, - parameters_len: parameter_len, - return_type: value.expected_return_type, + ffi, + _function_name: function_name, + _parameters: parameters, + _parameter_owners: parameter_owners, }) } + pub(crate) fn as_ffi(&self) -> &FfiFunctionCall { + &self.ffi + } +} + +impl FfiFunctionCall { /// Copies the parameters of `self` into a new `Vec`. /// # Safety - /// `self` must be an unmodified version of what `from_function_call` returned. + /// Every pointer in `self` must reference a live value of the declared + /// length. pub unsafe fn copy_parameters(&self) -> Vec { - let slice = unsafe { slice::from_raw_parts(self.parameters, self.parameters_len) }; + let slice = if self.parameters_len == 0 { + &[] + } else { + // SAFETY: required by the caller. + unsafe { slice::from_raw_parts(self.parameters, self.parameters_len) } + }; slice .iter() .map(|param| unsafe { param.copy_to_parameter_value() }) .collect() } - /// Copies the function name of `self into a new `String`. + /// Copies the function name of `self` into a new `String`. /// # Safety - /// `self` must be an unmodified version of what `from_function_call` returned. + /// `function_name` must point to a live NUL-terminated string. pub unsafe fn copy_function_name(&self) -> String { unsafe { CStr::from_ptr(self.function_name) @@ -76,25 +102,8 @@ impl FfiFunctionCall { /// Copies the return type of `self` into a new `ReturnType`. /// # Safety - /// `self` must be an unmodified version of what `from_function_call` returned. + /// `return_type` must contain a valid [`ReturnType`] discriminant. pub unsafe fn copy_return_type(&self) -> ReturnType { self.return_type } } - -impl Drop for FfiFunctionCall { - fn drop(&mut self) { - unsafe { - if !self.function_name.is_null() { - drop(CString::from_raw(self.function_name as *mut c_char)); - } - if !self.parameters.is_null() { - let slice = Box::from_raw(slice::from_raw_parts_mut( - self.parameters as *mut FfiParameter, - self.parameters_len, - )); - drop(slice); - } - } - } -} diff --git a/src/hyperlight_guest_capi/src/types/parameter.rs b/src/hyperlight_guest_capi/src/types/parameter.rs index daddd19eef..74f624be7f 100644 --- a/src/hyperlight_guest_capi/src/types/parameter.rs +++ b/src/hyperlight_guest_capi/src/types/parameter.rs @@ -2,12 +2,13 @@ // Copyright 2025 The Hyperlight Authors. use alloc::ffi::CString; +use alloc::vec::Vec; use core::ffi::{CStr, c_char}; use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterType, ParameterValue}; use hyperlight_guest::error::Result; -use crate::types::FfiVec; +use crate::types::{FfiByteChunks, FfiByteChunksOwner, FfiVec}; /// A union of the value stored in a ParameterValue, used for FFI. /// On it's own, this union has no way to know which value type is stored @@ -25,46 +26,112 @@ pub union FfiParameterValue { pub Bool: bool, pub String: *mut c_char, pub VecBytes: FfiVec, + pub ByteChunks: FfiByteChunks, } -/// An owned FFI version Of `ParameterValue` +/// An FFI view of a [`ParameterValue`]. #[repr(C)] +#[derive(Clone)] #[allow(non_camel_case_types)] pub struct FfiParameter { tag: ParameterType, value: FfiParameterValue, } -impl FfiParameter { - /// Returns a new `FfiParameter` by consuming a `ParameterValue` - pub fn from_parameter_value(value: ParameterValue) -> Result { - let (tag, union) = match value { - ParameterValue::Int(v) => (ParameterType::Int, FfiParameterValue { Int: v }), - ParameterValue::UInt(v) => (ParameterType::UInt, FfiParameterValue { UInt: v }), - ParameterValue::Long(v) => (ParameterType::Long, FfiParameterValue { Long: v }), - ParameterValue::ULong(v) => (ParameterType::ULong, FfiParameterValue { ULong: v }), - ParameterValue::Float(v) => (ParameterType::Float, FfiParameterValue { Float: v }), - ParameterValue::Double(v) => (ParameterType::Double, FfiParameterValue { Double: v }), - ParameterValue::Bool(v) => (ParameterType::Bool, FfiParameterValue { Bool: v }), +enum FfiParameterOwner { + None, + String { _value: CString }, + VecBytes { _value: Vec }, + ByteChunks { _value: FfiByteChunksOwner }, +} + +pub(crate) struct OwnedFfiParameter { + ffi: FfiParameter, + _owner: FfiParameterOwner, +} + +impl OwnedFfiParameter { + pub(crate) fn from_parameter_value(value: ParameterValue) -> Result { + let (tag, union, owner) = match value { + ParameterValue::Int(v) => ( + ParameterType::Int, + FfiParameterValue { Int: v }, + FfiParameterOwner::None, + ), + ParameterValue::UInt(v) => ( + ParameterType::UInt, + FfiParameterValue { UInt: v }, + FfiParameterOwner::None, + ), + ParameterValue::Long(v) => ( + ParameterType::Long, + FfiParameterValue { Long: v }, + FfiParameterOwner::None, + ), + ParameterValue::ULong(v) => ( + ParameterType::ULong, + FfiParameterValue { ULong: v }, + FfiParameterOwner::None, + ), + ParameterValue::Float(v) => ( + ParameterType::Float, + FfiParameterValue { Float: v }, + FfiParameterOwner::None, + ), + ParameterValue::Double(v) => ( + ParameterType::Double, + FfiParameterValue { Double: v }, + FfiParameterOwner::None, + ), + ParameterValue::Bool(v) => ( + ParameterType::Bool, + FfiParameterValue { Bool: v }, + FfiParameterOwner::None, + ), ParameterValue::String(v) => { - let c_str = CString::new(v.as_str()).expect("Unable to make CString from String"); - let leaked = c_str.into_raw(); - (ParameterType::String, FfiParameterValue { String: leaked }) + let value = CString::new(v.as_str()).expect("Unable to make CString from String"); + let ptr = value.as_ptr().cast_mut(); + ( + ParameterType::String, + FfiParameterValue { String: ptr }, + FfiParameterOwner::String { _value: value }, + ) } - ParameterValue::VecBytes(v) => { - let leaked = unsafe { FfiVec::from_vec(v) }; + ParameterValue::VecBytes(mut v) => { + let view = FfiVec::from_mut_slice(&mut v); ( ParameterType::VecBytes, - FfiParameterValue { VecBytes: leaked }, + FfiParameterValue { VecBytes: view }, + FfiParameterOwner::VecBytes { _value: v }, + ) + } + ParameterValue::ByteChunks(v) => { + let owner = FfiByteChunksOwner::new(v); + ( + ParameterType::ByteChunks, + FfiParameterValue { + ByteChunks: owner.view(), + }, + FfiParameterOwner::ByteChunks { _value: owner }, ) } }; - Ok(FfiParameter { tag, value: union }) + Ok(Self { + ffi: FfiParameter { tag, value: union }, + _owner: owner, + }) } + pub(crate) fn ffi(&self) -> FfiParameter { + self.ffi.clone() + } +} + +impl FfiParameter { /// Copies self into a new `ParameterValue`. /// # Safety - /// `self` must be an unmodified version of what `from_parameter_value` returned. + /// Every pointer selected by `tag` must reference a live value of the + /// declared length. pub unsafe fn copy_to_parameter_value(&self) -> ParameterValue { match self.tag { ParameterType::Int => ParameterValue::Int(unsafe { self.value.Int }), @@ -82,20 +149,44 @@ impl FfiParameter { ParameterType::VecBytes => { ParameterValue::VecBytes(unsafe { self.value.VecBytes.copy_to_vec() }) } + ParameterType::ByteChunks => { + // SAFETY: required by the caller. + ParameterValue::ByteChunks(unsafe { self.value.ByteChunks.copy_to_bytes() }) + } } } } -impl Drop for FfiParameter { - fn drop(&mut self) { - match self.tag { - ParameterType::String => unsafe { - drop(CString::from_raw(self.value.String)); - }, - ParameterType::VecBytes => unsafe { - drop(self.value.VecBytes.into_vec()); - }, - _ => {} - } +#[cfg(test)] +mod tests { + use alloc::vec; + + use hyperlight_common::flatbuffer_wrappers::function_types::Bytes; + + use super::*; + + #[test] + fn byte_chunks_parameter_is_borrowed_without_copying() { + let chunks = vec![Bytes::from_static(b"first"), Bytes::from_static(b"second")]; + let addresses = chunks.iter().map(Bytes::as_ptr).collect::>(); + let parameter = + OwnedFfiParameter::from_parameter_value(ParameterValue::ByteChunks(chunks)).unwrap(); + let ffi = parameter.ffi(); + + // SAFETY: `parameter` keeps its descriptor array and chunks alive. + let descriptors = unsafe { ffi.value.ByteChunks.as_slice() }; + assert_eq!(descriptors.len(), 2); + assert_eq!(descriptors[0].data(), addresses[0]); + assert_eq!(descriptors[1].data(), addresses[1]); + + // SAFETY: `parameter` keeps every pointer in `ffi` alive. + let copied = unsafe { ffi.copy_to_parameter_value() }; + assert_eq!( + copied, + ParameterValue::ByteChunks(vec![ + Bytes::from_static(b"first"), + Bytes::from_static(b"second") + ]) + ); } } diff --git a/src/hyperlight_guest_capi/src/types/return_value.rs b/src/hyperlight_guest_capi/src/types/return_value.rs new file mode 100644 index 0000000000..23cd163037 --- /dev/null +++ b/src/hyperlight_guest_capi/src/types/return_value.rs @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2026 The Hyperlight Authors. + +use alloc::borrow::ToOwned; +use alloc::ffi::CString; +use alloc::vec::Vec; +use core::ffi::{CStr, c_char}; +use core::mem::ManuallyDrop; + +use hyperlight_common::flatbuffer_wrappers::function_types::{ReturnType, ReturnValue}; + +use super::{FfiByteChunks, FfiVec}; + +/// The value held by an [`FfiReturnValue`]. +#[repr(C)] +#[derive(Copy, Clone)] +#[allow(non_camel_case_types, non_snake_case)] +pub union FfiReturnValueUnion { + pub Int: i32, + pub UInt: u32, + pub Long: i64, + pub ULong: u64, + pub Float: f32, + pub Double: f64, + pub Bool: bool, + pub String: *mut c_char, + pub VecBytes: FfiVec, + pub ByteChunks: FfiByteChunks, +} + +/// An owned FFI return value. +#[repr(C)] +#[allow(non_camel_case_types)] +pub struct FfiReturnValue { + tag: ReturnType, + value: FfiReturnValueUnion, +} + +impl FfiReturnValue { + pub fn int(value: i32) -> Self { + Self { + tag: ReturnType::Int, + value: FfiReturnValueUnion { Int: value }, + } + } + + pub fn uint(value: u32) -> Self { + Self { + tag: ReturnType::UInt, + value: FfiReturnValueUnion { UInt: value }, + } + } + + pub fn long(value: i64) -> Self { + Self { + tag: ReturnType::Long, + value: FfiReturnValueUnion { Long: value }, + } + } + + pub fn ulong(value: u64) -> Self { + Self { + tag: ReturnType::ULong, + value: FfiReturnValueUnion { ULong: value }, + } + } + + pub fn float(value: f32) -> Self { + Self { + tag: ReturnType::Float, + value: FfiReturnValueUnion { Float: value }, + } + } + + pub fn double(value: f64) -> Self { + Self { + tag: ReturnType::Double, + value: FfiReturnValueUnion { Double: value }, + } + } + + pub fn boolean(value: bool) -> Self { + Self { + tag: ReturnType::Bool, + value: FfiReturnValueUnion { Bool: value }, + } + } + + pub fn void() -> Self { + Self { + tag: ReturnType::Void, + value: FfiReturnValueUnion { Int: 0 }, + } + } + + pub fn string(value: &CStr) -> Self { + Self { + tag: ReturnType::String, + value: FfiReturnValueUnion { + String: value.to_owned().into_raw(), + }, + } + } + + pub fn vec_bytes(value: Vec) -> Self { + Self { + tag: ReturnType::VecBytes, + // SAFETY: `FfiReturnValue` reclaims the allocation when consumed or dropped. + value: FfiReturnValueUnion { + VecBytes: unsafe { FfiVec::from_vec(value) }, + }, + } + } + + /// # Safety + /// + /// Every pointer in `value` must reference its declared number of bytes. + pub unsafe fn byte_chunks(value: FfiByteChunks) -> Self { + Self { + tag: ReturnType::ByteChunks, + value: FfiReturnValueUnion { + // SAFETY: required by the caller. + ByteChunks: unsafe { value.copy_owned() }, + }, + } + } + + /// Consume this value and transfer its payload into a Rust return value. + /// + /// # Safety + /// + /// The tag and union value must be unchanged from a value created by this + /// type's constructors. + pub unsafe fn into_return_value(self) -> ReturnValue { + let value = ManuallyDrop::new(self); + // SAFETY: the contract requires the tag to identify the initialized union field. + unsafe { + match value.tag { + ReturnType::Int => ReturnValue::Int(value.value.Int), + ReturnType::UInt => ReturnValue::UInt(value.value.UInt), + ReturnType::Long => ReturnValue::Long(value.value.Long), + ReturnType::ULong => ReturnValue::ULong(value.value.ULong), + ReturnType::Float => ReturnValue::Float(value.value.Float), + ReturnType::Double => ReturnValue::Double(value.value.Double), + ReturnType::Bool => ReturnValue::Bool(value.value.Bool), + ReturnType::Void => ReturnValue::Void(()), + ReturnType::String => { + let value = CString::from_raw(value.value.String); + ReturnValue::String(value.to_string_lossy().into_owned()) + } + ReturnType::VecBytes => ReturnValue::VecBytes(value.value.VecBytes.into_vec()), + ReturnType::ByteChunks => { + ReturnValue::ByteChunks(value.value.ByteChunks.into_owned_bytes()) + } + } + } + } +} + +impl Drop for FfiReturnValue { + fn drop(&mut self) { + // SAFETY: constructors initialize the owned field selected by the tag. + unsafe { + match self.tag { + ReturnType::String => drop(CString::from_raw(self.value.String)), + ReturnType::VecBytes => drop(self.value.VecBytes.into_vec()), + ReturnType::ByteChunks => self.value.ByteChunks.drop_owned(), + _ => {} + } + } + } +} diff --git a/src/hyperlight_guest_capi/src/types/vec.rs b/src/hyperlight_guest_capi/src/types/vec.rs index 04f1206b72..b732800438 100644 --- a/src/hyperlight_guest_capi/src/types/vec.rs +++ b/src/hyperlight_guest_capi/src/types/vec.rs @@ -16,6 +16,17 @@ pub struct FfiVec { } impl FfiVec { + /// Creates a non-owning FFI view over `value` without copying. + /// + /// The caller must keep `value` alive and at a stable address while the + /// view is used. The view must not be passed to [`Self::into_vec`]. + pub(crate) fn from_mut_slice(value: &mut [u8]) -> Self { + Self { + data: value.as_mut_ptr(), + len: value.len(), + } + } + /// Creates a new `FfiVec` from the given Vec without copying memory. /// # Safety /// The caller must later reclaim memory by calling `into_vec`, otherwise memory will be leaked. @@ -42,21 +53,15 @@ impl FfiVec { res } - /// Copies the contents of `self` to a new independent Vec. + /// Copies the contents of `self` to a new independent `Vec`. /// # Safety - /// Self must have been obtained using `from_vec`, and must be in its original state (i.e. not modified). + /// `data` must reference `len` readable bytes when `len` is nonzero. pub unsafe fn copy_to_vec(&self) -> Vec { - // deconstruct - let slice = unsafe { slice::from_raw_parts_mut(self.data, self.len) }; - let boxed: Box<[u8]> = unsafe { Box::from_raw(slice) }; - let original = boxed.into_vec(); - // clone - let clone = original.clone(); - // reverse deconstruct - let boxed = original.into_boxed_slice(); - let leaked = Box::into_raw(boxed); - assert_eq!(self.data, leaked as *mut u8); - assert_eq!(self.len, leaked.len()); - clone + if self.len == 0 { + Vec::new() + } else { + // SAFETY: required by the caller. + unsafe { slice::from_raw_parts(self.data, self.len) }.to_vec() + } } } diff --git a/src/hyperlight_host/src/func/mod.rs b/src/hyperlight_host/src/func/mod.rs index d73dfed44c..40a70dd8a1 100644 --- a/src/hyperlight_host/src/func/mod.rs +++ b/src/hyperlight_host/src/func/mod.rs @@ -16,6 +16,8 @@ pub(crate) mod host_functions; /// Re-export for `HostFunction` trait pub use host_functions::{HostFunction, Registerable}; +/// Re-export for chunk-preserving byte values +pub use hyperlight_common::flatbuffer_wrappers::function_types::Bytes; /// Re-export for `ParameterType` enum pub use hyperlight_common::flatbuffer_wrappers::function_types::ParameterType; /// Re-export for `ParameterValue` enum diff --git a/src/hyperlight_host/src/sandbox/snapshot/file/config.rs b/src/hyperlight_host/src/sandbox/snapshot/file/config.rs index e9b49ef823..4faedf8c1a 100644 --- a/src/hyperlight_host/src/sandbox/snapshot/file/config.rs +++ b/src/hyperlight_host/src/sandbox/snapshot/file/config.rs @@ -246,6 +246,7 @@ enum ParameterTypeRepr { String, Bool, VecBytes, + ByteChunks, } /// JSON-friendly mirror of @@ -263,6 +264,7 @@ enum ReturnTypeRepr { Bool, Void, VecBytes, + ByteChunks, } impl From<&ParameterType> for ParameterTypeRepr { @@ -277,6 +279,7 @@ impl From<&ParameterType> for ParameterTypeRepr { ParameterType::String => Self::String, ParameterType::Bool => Self::Bool, ParameterType::VecBytes => Self::VecBytes, + ParameterType::ByteChunks => Self::ByteChunks, } } } @@ -293,6 +296,7 @@ impl From for ParameterType { ParameterTypeRepr::String => Self::String, ParameterTypeRepr::Bool => Self::Bool, ParameterTypeRepr::VecBytes => Self::VecBytes, + ParameterTypeRepr::ByteChunks => Self::ByteChunks, } } } @@ -310,6 +314,7 @@ impl From<&ReturnType> for ReturnTypeRepr { ReturnType::Bool => Self::Bool, ReturnType::Void => Self::Void, ReturnType::VecBytes => Self::VecBytes, + ReturnType::ByteChunks => Self::ByteChunks, } } } @@ -327,6 +332,7 @@ impl From for ReturnType { ReturnTypeRepr::Bool => Self::Bool, ReturnTypeRepr::Void => Self::Void, ReturnTypeRepr::VecBytes => Self::VecBytes, + ReturnTypeRepr::ByteChunks => Self::ByteChunks, } } } @@ -691,6 +697,7 @@ mod tests { ParameterType::String, ParameterType::Bool, ParameterType::VecBytes, + ParameterType::ByteChunks, ]; for p in variants { let back: ParameterType = ParameterTypeRepr::from(&p).into(); @@ -713,6 +720,7 @@ mod tests { ReturnType::Bool, ReturnType::Void, ReturnType::VecBytes, + ReturnType::ByteChunks, ]; for r in variants { let back: ReturnType = ReturnTypeRepr::from(&r).into(); diff --git a/src/schema/function_types.fbs b/src/schema/function_types.fbs index d5c209ff60..f078397a21 100644 --- a/src/schema/function_types.fbs +++ b/src/schema/function_types.fbs @@ -57,6 +57,22 @@ table hlvecbytes { value:[ubyte]; } +// hlbytechunks is the embedded compatibility representation of one logical +// byte sequence. It stores the bytes contiguously, so original chunk boundaries +// are not preserved. + +table hlbytechunks { + value:[ubyte]; +} + +// hlexternalbytes declares a logical byte value stored outside the FlatBuffer. +// chunked requests the ByteChunks representation rather than VecBytes. + +table hlexternalbytes { + length:ulong; + chunked:bool; +} + // hlsizeprefixedbuffer is a vector of bytes prefixed with a 32 bit integer table hlsizeprefixedbuffer { @@ -64,6 +80,14 @@ table hlsizeprefixedbuffer { value:[ubyte]; } +// hlsizeprefixedbytechunks is the embedded compatibility representation of a +// chunked return byte value. Embedded transport does not preserve boundaries. + +table hlsizeprefixedbytechunks { + size:int; + value:[ubyte]; +} + // hlvoid is a void (used for functions that return nothing) table hlvoid { @@ -81,6 +105,8 @@ union ParameterValue { hlstring, hlbool, hlvecbytes, + hlexternalbytes, + hlbytechunks, } // This represents a parameter type in a function definition @@ -95,6 +121,7 @@ enum ParameterType : ubyte { hlstring, hlbool, hlvecbytes, + hlbytechunks, } enum ReturnType : ubyte { @@ -108,6 +135,7 @@ enum ReturnType : ubyte { hlbool, hlvoid, hlsizeprefixedbuffer, + hlbytechunks, } union ReturnValue { @@ -121,4 +149,6 @@ union ReturnValue { hlbool, hlvoid, hlsizeprefixedbuffer, + hlexternalbytes, + hlsizeprefixedbytechunks, } diff --git a/src/tests/c_guests/c_simpleguest/main.c b/src/tests/c_guests/c_simpleguest/main.c index 91b4b974c8..6ac12d1692 100644 --- a/src/tests/c_guests/c_simpleguest/main.c +++ b/src/tests/c_guests/c_simpleguest/main.c @@ -29,13 +29,13 @@ int next_random(void) { return rand(); } long next_random_long(void) { return random(); } -hl_Vec *set_byte_array_to_zero(const hl_FunctionCall* params) { +hl_ReturnValue *set_byte_array_to_zero(const hl_FunctionCall* params) { hl_Vec input = params->parameters[0].value.VecBytes; uint8_t *x = malloc(input.len); for (uintptr_t i = 0; i < input.len; i++) { x[i] = 0; } - return hl_flatbuffer_result_from_Bytes(x, input.len); + return hl_result_from_Bytes(x, input.len); } int print_output(const char *message) { @@ -217,9 +217,9 @@ int set_static(void) { return length; } -hl_Vec *get_size_prefixed_buffer(const hl_FunctionCall* params) { +hl_ReturnValue *get_size_prefixed_buffer(const hl_FunctionCall* params) { hl_Vec input = params->parameters[0].value.VecBytes; - return hl_flatbuffer_result_from_Bytes(input.data, input.len); + return hl_result_from_Bytes(input.data, input.len); } int guest_abort_with_code(int32_t code) { @@ -243,10 +243,10 @@ int log_message(const char *message, int64_t level) { return -1; } -hl_Vec *twenty_four_k_in_eight_k_out(const hl_FunctionCall* params) { +hl_ReturnValue *twenty_four_k_in_eight_k_out(const hl_FunctionCall* params) { hl_Vec input = params->parameters[0].value.VecBytes; assert(input.len == 24 * 1024); - return hl_flatbuffer_result_from_Bytes(input.data, 8 * 1024); + return hl_result_from_Bytes(input.data, 8 * 1024); } int guest_function(const char *from_host) { @@ -421,12 +421,12 @@ void hyperlight_main(void) // This dispatch function is only used when the host dispatches a guest function // call but there is no registered guest function with the given name. -hl_Vec *c_guest_dispatch_function(const hl_FunctionCall *function_call) { +hl_ReturnValue *c_guest_dispatch_function(const hl_FunctionCall *function_call) { const char *func_name = function_call->function_name; if (strcmp(func_name, "ThisIsNotARealFunctionButTheNameIsImportant") == 0) { // TODO DO A LOG HERE - // This is special case for test `iostack_is_working - return hl_flatbuffer_result_from_Int(99); + // This is a special case for test `custom_guest_dispatch_is_working`. + return hl_result_from_Int(99); } return NULL; diff --git a/src/tests/rust_guests/simpleguest/src/main.rs b/src/tests/rust_guests/simpleguest/src/main.rs index d93d01a4a3..28f92ee75e 100644 --- a/src/tests/rust_guests/simpleguest/src/main.rs +++ b/src/tests/rust_guests/simpleguest/src/main.rs @@ -1581,6 +1581,7 @@ fn fuzz_host_function(func: FunctionCall) -> Result> { ReturnValue::Bool(bool) => Ok(get_flatbuffer_result(bool)), ReturnValue::Void(()) => Ok(get_flatbuffer_result(())), ReturnValue::VecBytes(byte) => Ok(get_flatbuffer_result(byte.as_slice())), + ReturnValue::ByteChunks(chunks) => Ok(get_flatbuffer_result(chunks)), }, Err(e) => Err(e), }