mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
rvm: switch binary serialization to postcard (#582)
Move RVM binary encoding from bincode to postcard and bump the format version. Update test helpers, docs, changelog, and refresh lockfiles after the swap. Closes #575 Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
b6f11c5602
commit
006e819d52
@@ -85,7 +85,7 @@ pub struct Program {
|
||||
|
||||
impl Program {
|
||||
/// Current serialization format version
|
||||
pub const SERIALIZATION_VERSION: u32 = 3;
|
||||
pub const SERIALIZATION_VERSION: u32 = 4;
|
||||
/// Magic bytes to identify Regorus program files
|
||||
pub const MAGIC: [u8; 4] = *b"REGO";
|
||||
/// Maximum instructions supported (matches u16 jump targets)
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
use alloc::format;
|
||||
use alloc::string::{String, ToString as _};
|
||||
use alloc::vec::Vec;
|
||||
use bincode::config::standard;
|
||||
use bincode::serde::{decode_from_slice, encode_to_vec};
|
||||
use postcard::{from_bytes, to_allocvec};
|
||||
|
||||
use super::{DeserializationResult, Program};
|
||||
use crate::value::Value;
|
||||
@@ -70,7 +69,7 @@ impl Program {
|
||||
}
|
||||
|
||||
/// Serialize program to binary format.
|
||||
/// Uses pure bincode for all sections now that `Value` supports serde.
|
||||
/// Uses postcard for all sections now that `Value` supports serde.
|
||||
pub fn serialize_binary(&self) -> Result<Vec<u8>, String> {
|
||||
self.validate_limits()?;
|
||||
|
||||
@@ -79,19 +78,19 @@ impl Program {
|
||||
buffer.extend_from_slice(&Self::MAGIC);
|
||||
buffer.extend_from_slice(&Self::SERIALIZATION_VERSION.to_le_bytes());
|
||||
|
||||
let entry_points_bin = encode_to_vec(&self.entry_points, standard())
|
||||
.map_err(|e| format!("Entry points bincode serialization failed: {}", e))?;
|
||||
let entry_points_bin = to_allocvec(&self.entry_points)
|
||||
.map_err(|e| format!("Entry points postcard serialization failed: {}", e))?;
|
||||
|
||||
let sources_bin = encode_to_vec(&self.sources, standard())
|
||||
.map_err(|e| format!("Sources bincode serialization failed: {}", e))?;
|
||||
let sources_bin = to_allocvec(&self.sources)
|
||||
.map_err(|e| format!("Sources postcard serialization failed: {}", e))?;
|
||||
|
||||
let literals_bin = encode_to_vec(BinaryValueSlice(self.literals.as_slice()), standard())
|
||||
.map_err(|e| format!("Literals bincode serialization failed: {}", e))?;
|
||||
let literals_bin = to_allocvec(&BinaryValueSlice(self.literals.as_slice()))
|
||||
.map_err(|e| format!("Literals postcard serialization failed: {}", e))?;
|
||||
|
||||
let rule_tree_bin = encode_to_vec(BinaryValueRef(&self.rule_tree), standard())
|
||||
.map_err(|e| format!("Rule tree bincode serialization failed: {}", e))?;
|
||||
let rule_tree_bin = to_allocvec(&BinaryValueRef(&self.rule_tree))
|
||||
.map_err(|e| format!("Rule tree postcard serialization failed: {}", e))?;
|
||||
|
||||
let binary_data = encode_to_vec(self, standard())
|
||||
let binary_data = to_allocvec(self)
|
||||
.map_err(|e| format!("Program structure binary serialization failed: {}", e))?;
|
||||
|
||||
buffer.extend_from_slice(
|
||||
@@ -152,128 +151,13 @@ impl Program {
|
||||
}
|
||||
|
||||
match version {
|
||||
1 => {
|
||||
if data.len() < 25 {
|
||||
return Err("Data too short for header".to_string());
|
||||
}
|
||||
|
||||
let entry_points_len = Self::read_u32_as_usize(data, 8)?;
|
||||
let sources_len = Self::read_u32_as_usize(data, 12)?;
|
||||
let rego_v0 = Self::read_byte(data, 16)? != 0;
|
||||
|
||||
let mut offset = OffsetTracker::new(17);
|
||||
let entry_points_start = offset.advance(entry_points_len)?;
|
||||
let sources_start = offset.advance(sources_len)?;
|
||||
let binary_len_start = offset.current();
|
||||
|
||||
if data.len() < binary_len_start.checked_add(4).ok_or("Offset overflow")? {
|
||||
return Err("Data too short for binary length".to_string());
|
||||
}
|
||||
|
||||
let binary_len = Self::read_u32_as_usize(data, binary_len_start)?;
|
||||
|
||||
let mut binary_offset = OffsetTracker::new(binary_len_start);
|
||||
binary_offset.advance(4)?; // Skip the binary_len u32
|
||||
let binary_start = binary_offset.advance(binary_len)?;
|
||||
let json_start = binary_offset.current();
|
||||
|
||||
if data.len() < json_start.checked_add(4).ok_or("Offset overflow")? {
|
||||
return Err("Data too short for JSON length".to_string());
|
||||
}
|
||||
|
||||
let json_len = Self::read_u32_as_usize(data, json_start)?;
|
||||
|
||||
let total_expected = json_start
|
||||
.checked_add(4)
|
||||
.and_then(|v| v.checked_add(json_len))
|
||||
.ok_or("Offset overflow")?;
|
||||
if data.len() < total_expected {
|
||||
return Err("Data truncated".to_string());
|
||||
}
|
||||
|
||||
let json_end = json_start
|
||||
.checked_add(4)
|
||||
.and_then(|v| v.checked_add(json_len))
|
||||
.ok_or("Offset overflow")?;
|
||||
|
||||
let entry_points = decode_from_slice(
|
||||
Self::get_slice(data, entry_points_start, sources_start)?,
|
||||
standard(),
|
||||
)
|
||||
.map(|(value, _)| value)
|
||||
.map_err(|e| format!("Entry points deserialization failed: {}", e))?;
|
||||
|
||||
let sources = decode_from_slice(
|
||||
Self::get_slice(data, sources_start, binary_len_start)?,
|
||||
standard(),
|
||||
)
|
||||
.map(|(value, _)| value)
|
||||
.map_err(|e| format!("Sources deserialization failed: {}", e))?;
|
||||
|
||||
let mut needs_recompilation = false;
|
||||
|
||||
let mut program = match decode_from_slice::<Program, _>(
|
||||
Self::get_slice(data, binary_start, json_start)?,
|
||||
standard(),
|
||||
) {
|
||||
Ok((prog, _)) => prog,
|
||||
Err(_e) => {
|
||||
needs_recompilation = true;
|
||||
Program::new()
|
||||
}
|
||||
};
|
||||
|
||||
let (literals, rule_tree) =
|
||||
match serde_json::from_slice::<serde_json::Value>(Self::get_slice(
|
||||
data,
|
||||
json_start.checked_add(4).ok_or("Offset overflow")?,
|
||||
json_end,
|
||||
)?) {
|
||||
Ok(combined) => {
|
||||
let literals = combined
|
||||
.get("literals")
|
||||
.and_then(|v| serde_json::from_value::<Vec<Value>>(v.clone()).ok())
|
||||
.unwrap_or_else(|| {
|
||||
needs_recompilation = true;
|
||||
Vec::new()
|
||||
});
|
||||
|
||||
let rule_tree = combined
|
||||
.get("rule_tree")
|
||||
.and_then(|v| serde_json::from_value::<Value>(v.clone()).ok())
|
||||
.unwrap_or_else(|| {
|
||||
needs_recompilation = true;
|
||||
Value::new_object()
|
||||
});
|
||||
|
||||
(literals, rule_tree)
|
||||
}
|
||||
Err(_e) => {
|
||||
needs_recompilation = true;
|
||||
(Vec::new(), Value::new_object())
|
||||
}
|
||||
};
|
||||
|
||||
program.entry_points = entry_points;
|
||||
program.sources = sources;
|
||||
program.literals = literals;
|
||||
program.rule_tree = rule_tree;
|
||||
program.rego_v0 = rego_v0;
|
||||
program.needs_recompilation = needs_recompilation;
|
||||
|
||||
if !program.builtin_info_table.is_empty() {
|
||||
if let Err(_e) = program.initialize_resolved_builtins() {
|
||||
program.needs_recompilation = true;
|
||||
}
|
||||
}
|
||||
|
||||
if program.needs_recompilation {
|
||||
Ok(DeserializationResult::Partial(program))
|
||||
} else {
|
||||
Ok(DeserializationResult::Complete(program))
|
||||
}
|
||||
1..=3 => {
|
||||
let mut program = Program::new();
|
||||
program.needs_recompilation = true;
|
||||
program.rego_v0 = Self::legacy_rego_v0(data, version).unwrap_or(false);
|
||||
Ok(DeserializationResult::Partial(program))
|
||||
}
|
||||
2 | 3 => {
|
||||
4 => {
|
||||
if data.len() < 29 {
|
||||
return Err("Data too short for header".to_string());
|
||||
}
|
||||
@@ -306,27 +190,21 @@ impl Program {
|
||||
return Err("Data truncated".to_string());
|
||||
}
|
||||
|
||||
let entry_points = decode_from_slice(
|
||||
Self::get_slice(data, entry_points_start, sources_start)?,
|
||||
standard(),
|
||||
)
|
||||
.map(|(value, _)| value)
|
||||
.map_err(|e| format!("Entry points deserialization failed: {}", e))?;
|
||||
let entry_points =
|
||||
from_bytes(Self::get_slice(data, entry_points_start, sources_start)?)
|
||||
.map_err(|e| format!("Entry points deserialization failed: {}", e))?;
|
||||
|
||||
let sources = decode_from_slice(
|
||||
Self::get_slice(data, sources_start, literals_start)?,
|
||||
standard(),
|
||||
)
|
||||
.map(|(value, _)| value)
|
||||
.map_err(|e| format!("Sources deserialization failed: {}", e))?;
|
||||
let sources = from_bytes(Self::get_slice(data, sources_start, literals_start)?)
|
||||
.map_err(|e| format!("Sources deserialization failed: {}", e))?;
|
||||
|
||||
let mut needs_recompilation = false;
|
||||
|
||||
let literals = match decode_from_slice::<Vec<BinaryValue>, _>(
|
||||
Self::get_slice(data, literals_start, rule_tree_start)?,
|
||||
standard(),
|
||||
) {
|
||||
Ok((binary_literals, _)) => match binaries_to_values(binary_literals) {
|
||||
let literals = match from_bytes::<Vec<BinaryValue>>(Self::get_slice(
|
||||
data,
|
||||
literals_start,
|
||||
rule_tree_start,
|
||||
)?) {
|
||||
Ok(binary_literals) => match binaries_to_values(binary_literals) {
|
||||
Ok(values) => values,
|
||||
Err(_e) => {
|
||||
needs_recompilation = true;
|
||||
@@ -339,11 +217,12 @@ impl Program {
|
||||
}
|
||||
};
|
||||
|
||||
let rule_tree = match decode_from_slice::<BinaryValue, _>(
|
||||
Self::get_slice(data, rule_tree_start, binary_len_start)?,
|
||||
standard(),
|
||||
) {
|
||||
Ok((binary_tree, _)) => match binary_to_value(binary_tree) {
|
||||
let rule_tree = match from_bytes::<BinaryValue>(Self::get_slice(
|
||||
data,
|
||||
rule_tree_start,
|
||||
binary_len_start,
|
||||
)?) {
|
||||
Ok(binary_tree) => match binary_to_value(binary_tree) {
|
||||
Ok(value) => value,
|
||||
Err(_e) => {
|
||||
needs_recompilation = true;
|
||||
@@ -356,16 +235,14 @@ impl Program {
|
||||
}
|
||||
};
|
||||
|
||||
let mut program = match decode_from_slice::<Program, _>(
|
||||
Self::get_slice(data, binary_start, binary_end)?,
|
||||
standard(),
|
||||
) {
|
||||
Ok((prog, _)) => prog,
|
||||
Err(_e) => {
|
||||
needs_recompilation = true;
|
||||
Program::new()
|
||||
}
|
||||
};
|
||||
let mut program =
|
||||
match from_bytes::<Program>(Self::get_slice(data, binary_start, binary_end)?) {
|
||||
Ok(prog) => prog,
|
||||
Err(_e) => {
|
||||
needs_recompilation = true;
|
||||
Program::new()
|
||||
}
|
||||
};
|
||||
|
||||
program.entry_points = entry_points;
|
||||
program.sources = sources;
|
||||
@@ -404,8 +281,16 @@ impl Program {
|
||||
let version = Self::read_u32(data, 4).ok();
|
||||
|
||||
match version {
|
||||
Some(1..=3) => Ok(true),
|
||||
Some(1..=4) => Ok(true),
|
||||
_ => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
fn legacy_rego_v0(data: &[u8], version: u32) -> Option<bool> {
|
||||
match version {
|
||||
1 => data.get(16).map(|value| *value != 0),
|
||||
2 | 3 => data.get(24).map(|value| *value != 0),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,7 @@ use crate::rvm::program::{binaries_to_values, BinaryValue, Program};
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use bincode::config::standard;
|
||||
use bincode::serde::decode_from_slice;
|
||||
use postcard::from_bytes;
|
||||
|
||||
/// Test utility function for round-trip serialization
|
||||
/// Serializes program, deserializes it, and serializes again to check for consistency
|
||||
@@ -31,7 +30,7 @@ pub fn test_round_trip_serialization(program: &Program) -> Result<(), String> {
|
||||
serialized1[7],
|
||||
]);
|
||||
|
||||
if version == 2 && serialized1.len() >= 25 {
|
||||
if version == Program::SERIALIZATION_VERSION && serialized1.len() >= 25 {
|
||||
let entry_points_len = u32::from_le_bytes([
|
||||
serialized1[8],
|
||||
serialized1[9],
|
||||
@@ -56,11 +55,9 @@ pub fn test_round_trip_serialization(program: &Program) -> Result<(), String> {
|
||||
let rule_tree_start = literals_start + literals_len;
|
||||
|
||||
if literals_len > 0 && serialized1.len() >= rule_tree_start {
|
||||
match decode_from_slice::<Vec<BinaryValue>, _>(
|
||||
&serialized1[literals_start..rule_tree_start],
|
||||
standard(),
|
||||
) {
|
||||
Ok((decoded_literals, _)) => {
|
||||
match from_bytes::<Vec<BinaryValue>>(&serialized1[literals_start..rule_tree_start])
|
||||
{
|
||||
Ok(decoded_literals) => {
|
||||
if binaries_to_values(decoded_literals).is_err() {
|
||||
return Err(
|
||||
"Failed to convert literal table from binary representation".into(),
|
||||
@@ -69,7 +66,7 @@ pub fn test_round_trip_serialization(program: &Program) -> Result<(), String> {
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(format!(
|
||||
"Failed to decode literal table with bincode: {}",
|
||||
"Failed to decode literal table with postcard: {}",
|
||||
err
|
||||
));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user