hyperlight_common/flatbuffer_wrappers/
util.rs

1/*
2Copyright 2025  The Hyperlight Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17use alloc::vec::Vec;
18
19use flatbuffers::FlatBufferBuilder;
20
21use crate::flatbuffer_wrappers::function_types::ParameterValue;
22use crate::flatbuffers::hyperlight::generated::{
23    FunctionCallResult as FbFunctionCallResult, FunctionCallResultArgs as FbFunctionCallResultArgs,
24    ReturnValue as FbReturnValue, hlbool as Fbhlbool, hlboolArgs as FbhlboolArgs,
25    hldouble as Fbhldouble, hldoubleArgs as FbhldoubleArgs, hlfloat as Fbhlfloat,
26    hlfloatArgs as FbhlfloatArgs, hlint as Fbhlint, hlintArgs as FbhlintArgs, hllong as Fbhllong,
27    hllongArgs as FbhllongArgs, hlsizeprefixedbuffer as Fbhlsizeprefixedbuffer,
28    hlsizeprefixedbufferArgs as FbhlsizeprefixedbufferArgs, hlstring as Fbhlstring,
29    hlstringArgs as FbhlstringArgs, hluint as Fbhluint, hluintArgs as FbhluintArgs,
30    hlulong as Fbhlulong, hlulongArgs as FbhlulongArgs, hlvoid as Fbhlvoid,
31    hlvoidArgs as FbhlvoidArgs,
32};
33
34/// Flatbuffer-encodes the given value
35pub fn get_flatbuffer_result<T: FlatbufferSerializable>(val: T) -> Vec<u8> {
36    let mut builder = FlatBufferBuilder::new();
37    let res = &T::serialize(&val, &mut builder);
38    let result_offset = FbFunctionCallResult::create(&mut builder, res);
39
40    builder.finish_size_prefixed(result_offset, None);
41
42    builder.finished_data().to_vec()
43}
44
45pub trait FlatbufferSerializable {
46    fn serialize(&self, builder: &mut FlatBufferBuilder) -> FbFunctionCallResultArgs;
47}
48
49// Implementations for basic types below
50
51impl FlatbufferSerializable for () {
52    fn serialize(&self, builder: &mut FlatBufferBuilder) -> FbFunctionCallResultArgs {
53        FbFunctionCallResultArgs {
54            return_value: Some(Fbhlvoid::create(builder, &FbhlvoidArgs {}).as_union_value()),
55            return_value_type: FbReturnValue::hlvoid,
56        }
57    }
58}
59
60impl FlatbufferSerializable for &str {
61    fn serialize(&self, builder: &mut FlatBufferBuilder) -> FbFunctionCallResultArgs {
62        let string_offset = builder.create_string(self);
63        FbFunctionCallResultArgs {
64            return_value: Some(
65                Fbhlstring::create(
66                    builder,
67                    &FbhlstringArgs {
68                        value: Some(string_offset),
69                    },
70                )
71                .as_union_value(),
72            ),
73            return_value_type: FbReturnValue::hlstring,
74        }
75    }
76}
77
78impl FlatbufferSerializable for &[u8] {
79    fn serialize(&self, builder: &mut FlatBufferBuilder) -> FbFunctionCallResultArgs {
80        let vec_offset = builder.create_vector(self);
81        FbFunctionCallResultArgs {
82            return_value: Some(
83                Fbhlsizeprefixedbuffer::create(
84                    builder,
85                    &FbhlsizeprefixedbufferArgs {
86                        size: self.len() as i32,
87                        value: Some(vec_offset),
88                    },
89                )
90                .as_union_value(),
91            ),
92            return_value_type: FbReturnValue::hlsizeprefixedbuffer,
93        }
94    }
95}
96
97impl FlatbufferSerializable for f32 {
98    fn serialize(&self, builder: &mut FlatBufferBuilder) -> FbFunctionCallResultArgs {
99        FbFunctionCallResultArgs {
100            return_value: Some(
101                Fbhlfloat::create(builder, &FbhlfloatArgs { value: *self }).as_union_value(),
102            ),
103            return_value_type: FbReturnValue::hlfloat,
104        }
105    }
106}
107
108impl FlatbufferSerializable for f64 {
109    fn serialize(&self, builder: &mut FlatBufferBuilder) -> FbFunctionCallResultArgs {
110        FbFunctionCallResultArgs {
111            return_value: Some(
112                Fbhldouble::create(builder, &FbhldoubleArgs { value: *self }).as_union_value(),
113            ),
114            return_value_type: FbReturnValue::hldouble,
115        }
116    }
117}
118
119impl FlatbufferSerializable for i32 {
120    fn serialize(&self, builder: &mut FlatBufferBuilder) -> FbFunctionCallResultArgs {
121        FbFunctionCallResultArgs {
122            return_value: Some(
123                Fbhlint::create(builder, &FbhlintArgs { value: *self }).as_union_value(),
124            ),
125            return_value_type: FbReturnValue::hlint,
126        }
127    }
128}
129
130impl FlatbufferSerializable for i64 {
131    fn serialize(&self, builder: &mut FlatBufferBuilder) -> FbFunctionCallResultArgs {
132        FbFunctionCallResultArgs {
133            return_value: Some(
134                Fbhllong::create(builder, &FbhllongArgs { value: *self }).as_union_value(),
135            ),
136            return_value_type: FbReturnValue::hllong,
137        }
138    }
139}
140
141impl FlatbufferSerializable for u32 {
142    fn serialize(&self, builder: &mut FlatBufferBuilder) -> FbFunctionCallResultArgs {
143        FbFunctionCallResultArgs {
144            return_value: Some(
145                Fbhluint::create(builder, &FbhluintArgs { value: *self }).as_union_value(),
146            ),
147            return_value_type: FbReturnValue::hluint,
148        }
149    }
150}
151
152impl FlatbufferSerializable for u64 {
153    fn serialize(&self, builder: &mut FlatBufferBuilder) -> FbFunctionCallResultArgs {
154        FbFunctionCallResultArgs {
155            return_value: Some(
156                Fbhlulong::create(builder, &FbhlulongArgs { value: *self }).as_union_value(),
157            ),
158            return_value_type: FbReturnValue::hlulong,
159        }
160    }
161}
162
163impl FlatbufferSerializable for bool {
164    fn serialize(&self, builder: &mut FlatBufferBuilder) -> FbFunctionCallResultArgs {
165        FbFunctionCallResultArgs {
166            return_value: Some(
167                Fbhlbool::create(builder, &FbhlboolArgs { value: *self }).as_union_value(),
168            ),
169            return_value_type: FbReturnValue::hlbool,
170        }
171    }
172}
173
174/// Estimates the required buffer capacity for encoding a FunctionCall with the given parameters.
175/// This helps avoid reallocation during FlatBuffer encoding when passing large slices and strings.
176///
177/// The function aims to be lightweight and fast and run in O(1) as long as the number of parameters is limited
178/// (which it is since hyperlight only currently supports up to 12).
179///
180/// Note: This estimates the capacity needed for the inner vec inside a FlatBufferBuilder. It does not
181/// necessarily match the size of the final encoded buffer. The estimation always rounds up to the
182/// nearest power of two to match FlatBufferBuilder's allocation strategy.
183///
184/// The estimations are numbers used are empirically derived based on the tests below and vaguely based
185/// on https://flatbuffers.dev/internals/ and https://github.com/dvidelabs/flatcc/blob/f064cefb2034d1e7407407ce32a6085c322212a7/doc/binary-format.md#flatbuffers-binary-format
186#[inline] // allow cross-crate inlining (for hyperlight-host calls)
187pub fn estimate_flatbuffer_capacity(function_name: &str, args: &[ParameterValue]) -> usize {
188    let mut estimated_capacity = 20;
189
190    // Function name overhead
191    estimated_capacity += function_name.len() + 12;
192
193    // Parameters vector overhead
194    estimated_capacity += 12 + args.len() * 6;
195
196    // Per-parameter overhead
197    for arg in args {
198        estimated_capacity += 16; // Base parameter structure
199        estimated_capacity += match arg {
200            ParameterValue::String(s) => s.len() + 20,
201            ParameterValue::VecBytes(v) => v.len() + 20,
202            ParameterValue::Int(_) | ParameterValue::UInt(_) => 16,
203            ParameterValue::Long(_) | ParameterValue::ULong(_) => 20,
204            ParameterValue::Float(_) => 16,
205            ParameterValue::Double(_) => 20,
206            ParameterValue::Bool(_) => 12,
207        };
208    }
209
210    // match how vec grows
211    estimated_capacity.next_power_of_two()
212}
213
214#[cfg(test)]
215mod tests {
216    use alloc::string::ToString;
217    use alloc::vec;
218    use alloc::vec::Vec;
219
220    use super::*;
221    use crate::flatbuffer_wrappers::function_call::{FunctionCall, FunctionCallType};
222    use crate::flatbuffer_wrappers::function_types::{ParameterValue, ReturnType};
223
224    /// Helper function to check that estimation is within reasonable bounds (±25%)
225    fn assert_estimation_accuracy(
226        function_name: &str,
227        args: Vec<ParameterValue>,
228        call_type: FunctionCallType,
229        return_type: ReturnType,
230    ) {
231        let estimated = estimate_flatbuffer_capacity(function_name, &args);
232
233        let fc = FunctionCall::new(
234            function_name.to_string(),
235            Some(args),
236            call_type.clone(),
237            return_type,
238        );
239        // Important that this FlatBufferBuilder is created with capacity 0 so it grows to its needed capacity
240        let mut builder = FlatBufferBuilder::new();
241        let _buffer = fc.encode(&mut builder);
242        let actual = builder.collapse().0.capacity();
243
244        let lower_bound = (actual as f64 * 0.75) as usize;
245        let upper_bound = (actual as f64 * 1.25) as usize;
246
247        assert!(
248            estimated >= lower_bound && estimated <= upper_bound,
249            "Estimation {} outside bounds [{}, {}] for actual size {} (function: {}, call_type: {:?}, return_type: {:?})",
250            estimated,
251            lower_bound,
252            upper_bound,
253            actual,
254            function_name,
255            call_type,
256            return_type
257        );
258    }
259
260    #[test]
261    fn test_estimate_no_parameters() {
262        assert_estimation_accuracy(
263            "simple_function",
264            vec![],
265            FunctionCallType::Guest,
266            ReturnType::Void,
267        );
268    }
269
270    #[test]
271    fn test_estimate_single_int_parameter() {
272        assert_estimation_accuracy(
273            "add_one",
274            vec![ParameterValue::Int(42)],
275            FunctionCallType::Guest,
276            ReturnType::Int,
277        );
278    }
279
280    #[test]
281    fn test_estimate_multiple_scalar_parameters() {
282        assert_estimation_accuracy(
283            "calculate",
284            vec![
285                ParameterValue::Int(10),
286                ParameterValue::UInt(20),
287                ParameterValue::Long(30),
288                ParameterValue::ULong(40),
289                ParameterValue::Float(1.5),
290                ParameterValue::Double(2.5),
291                ParameterValue::Bool(true),
292            ],
293            FunctionCallType::Guest,
294            ReturnType::Double,
295        );
296    }
297
298    #[test]
299    fn test_estimate_string_parameters() {
300        assert_estimation_accuracy(
301            "process_strings",
302            vec![
303                ParameterValue::String("hello".to_string()),
304                ParameterValue::String("world".to_string()),
305                ParameterValue::String("this is a longer string for testing".to_string()),
306            ],
307            FunctionCallType::Host,
308            ReturnType::String,
309        );
310    }
311
312    #[test]
313    fn test_estimate_very_long_string() {
314        let long_string = "a".repeat(1000);
315        assert_estimation_accuracy(
316            "process_long_string",
317            vec![ParameterValue::String(long_string)],
318            FunctionCallType::Guest,
319            ReturnType::String,
320        );
321    }
322
323    #[test]
324    fn test_estimate_vector_parameters() {
325        assert_estimation_accuracy(
326            "process_vectors",
327            vec![
328                ParameterValue::VecBytes(vec![1, 2, 3, 4, 5]),
329                ParameterValue::VecBytes(vec![]),
330                ParameterValue::VecBytes(vec![0; 100]),
331            ],
332            FunctionCallType::Host,
333            ReturnType::VecBytes,
334        );
335    }
336
337    #[test]
338    fn test_estimate_mixed_parameters() {
339        assert_estimation_accuracy(
340            "complex_function",
341            vec![
342                ParameterValue::String("test".to_string()),
343                ParameterValue::Int(42),
344                ParameterValue::VecBytes(vec![1, 2, 3, 4, 5]),
345                ParameterValue::Bool(true),
346                ParameterValue::Double(553.14159),
347                ParameterValue::String("another string".to_string()),
348                ParameterValue::Long(9223372036854775807),
349            ],
350            FunctionCallType::Guest,
351            ReturnType::VecBytes,
352        );
353    }
354
355    #[test]
356    fn test_estimate_large_function_name() {
357        let long_name = "very_long_function_name_that_exceeds_normal_lengths_for_testing_purposes";
358        assert_estimation_accuracy(
359            long_name,
360            vec![ParameterValue::Int(1)],
361            FunctionCallType::Host,
362            ReturnType::Long,
363        );
364    }
365
366    #[test]
367    fn test_estimate_large_vector() {
368        let large_vector = vec![42u8; 10000];
369        assert_estimation_accuracy(
370            "process_large_data",
371            vec![ParameterValue::VecBytes(large_vector)],
372            FunctionCallType::Guest,
373            ReturnType::Bool,
374        );
375    }
376
377    #[test]
378    fn test_estimate_all_parameter_types() {
379        assert_estimation_accuracy(
380            "comprehensive_test",
381            vec![
382                ParameterValue::Int(i32::MIN),
383                ParameterValue::UInt(u32::MAX),
384                ParameterValue::Long(i64::MIN),
385                ParameterValue::ULong(u64::MAX),
386                ParameterValue::Float(f32::MIN),
387                ParameterValue::Double(f64::MAX),
388                ParameterValue::Bool(false),
389                ParameterValue::String("test string".to_string()),
390                ParameterValue::VecBytes(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
391            ],
392            FunctionCallType::Host,
393            ReturnType::ULong,
394        );
395    }
396
397    #[test]
398    fn test_different_function_call_types() {
399        assert_estimation_accuracy(
400            "guest_function",
401            vec![ParameterValue::String("guest call".to_string())],
402            FunctionCallType::Guest,
403            ReturnType::String,
404        );
405
406        assert_estimation_accuracy(
407            "host_function",
408            vec![ParameterValue::String("host call".to_string())],
409            FunctionCallType::Host,
410            ReturnType::String,
411        );
412    }
413
414    #[test]
415    fn test_different_return_types() {
416        let args = vec![
417            ParameterValue::Int(42),
418            ParameterValue::String("test".to_string()),
419        ];
420
421        let void_est = estimate_flatbuffer_capacity("test_void", &args);
422        let int_est = estimate_flatbuffer_capacity("test_int", &args);
423        let string_est = estimate_flatbuffer_capacity("test_string", &args);
424
425        assert!((void_est as i32 - int_est as i32).abs() < 10);
426        assert!((int_est as i32 - string_est as i32).abs() < 10);
427
428        assert_estimation_accuracy(
429            "test_void",
430            args.clone(),
431            FunctionCallType::Guest,
432            ReturnType::Void,
433        );
434        assert_estimation_accuracy(
435            "test_int",
436            args.clone(),
437            FunctionCallType::Guest,
438            ReturnType::Int,
439        );
440        assert_estimation_accuracy(
441            "test_string",
442            args,
443            FunctionCallType::Guest,
444            ReturnType::String,
445        );
446    }
447
448    #[test]
449    fn test_estimate_many_large_vectors_and_strings() {
450        assert_estimation_accuracy(
451            "process_bulk_data",
452            vec![
453                ParameterValue::String("Large string data: ".to_string() + &"x".repeat(2000)),
454                ParameterValue::VecBytes(vec![1u8; 5000]),
455                ParameterValue::String(
456                    "Another large string with lots of content ".to_string() + &"y".repeat(3000),
457                ),
458                ParameterValue::VecBytes(vec![255u8; 7500]),
459                ParameterValue::String(
460                    "Third massive string parameter ".to_string() + &"z".repeat(1500),
461                ),
462                ParameterValue::VecBytes(vec![128u8; 10000]),
463                ParameterValue::Int(42),
464                ParameterValue::String("Final large string ".to_string() + &"a".repeat(4000)),
465                ParameterValue::VecBytes(vec![64u8; 2500]),
466                ParameterValue::Bool(true),
467            ],
468            FunctionCallType::Host,
469            ReturnType::VecBytes,
470        );
471    }
472
473    #[test]
474    fn test_estimate_twenty_parameters() {
475        assert_estimation_accuracy(
476            "function_with_many_parameters",
477            vec![
478                ParameterValue::Int(1),
479                ParameterValue::String("param2".to_string()),
480                ParameterValue::Bool(true),
481                ParameterValue::Float(3213.14),
482                ParameterValue::VecBytes(vec![1, 2, 3]),
483                ParameterValue::Long(1000000),
484                ParameterValue::Double(322.718),
485                ParameterValue::UInt(42),
486                ParameterValue::String("param9".to_string()),
487                ParameterValue::Bool(false),
488                ParameterValue::ULong(9999999999),
489                ParameterValue::VecBytes(vec![4, 5, 6, 7, 8]),
490                ParameterValue::Int(-100),
491                ParameterValue::Float(1.414),
492                ParameterValue::String("param15".to_string()),
493                ParameterValue::Double(1.732),
494                ParameterValue::Bool(true),
495                ParameterValue::VecBytes(vec![9, 10]),
496                ParameterValue::Long(-5000000),
497                ParameterValue::UInt(12345),
498            ],
499            FunctionCallType::Guest,
500            ReturnType::Int,
501        );
502    }
503
504    #[test]
505    fn test_estimate_megabyte_parameters() {
506        assert_estimation_accuracy(
507            "process_megabyte_data",
508            vec![
509                ParameterValue::String("MB String 1: ".to_string() + &"x".repeat(1_048_576)), // 1MB string
510                ParameterValue::VecBytes(vec![42u8; 2_097_152]), // 2MB vector
511                ParameterValue::String("MB String 2: ".to_string() + &"y".repeat(1_572_864)), // 1.5MB string
512                ParameterValue::VecBytes(vec![128u8; 3_145_728]), // 3MB vector
513                ParameterValue::String("MB String 3: ".to_string() + &"z".repeat(2_097_152)), // 2MB string
514            ],
515            FunctionCallType::Host,
516            ReturnType::VecBytes,
517        );
518    }
519}