From 01c3c4e44fd39d7c97ce1c78c927488f8f8c9b51 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Mon, 2 Sep 2024 15:28:52 +0200 Subject: [PATCH 1/2] Fix --- Cargo.lock | 1 + contracts/burner/Cargo.lock | 1 + contracts/crypto-verify/Cargo.lock | 1 + contracts/cyberpunk/Cargo.lock | 1 + contracts/empty/Cargo.lock | 1 + contracts/floaty/Cargo.lock | 1 + contracts/hackatom/Cargo.lock | 1 + contracts/ibc-callbacks/Cargo.lock | 1 + contracts/ibc-reflect-send/Cargo.lock | 1 + contracts/ibc-reflect/Cargo.lock | 1 + contracts/queue/Cargo.lock | 1 + contracts/reflect/Cargo.lock | 1 + contracts/staking/Cargo.lock | 1 + contracts/virus/Cargo.lock | 1 + packages/vm/Cargo.toml | 1 + packages/vm/src/instance.rs | 8 +- packages/vm/src/wasm_backend/engine.rs | 17 +- packages/vm/src/wasm_backend/metering.rs | 265 +++++++++++++++++++++++ packages/vm/src/wasm_backend/mod.rs | 1 + 19 files changed, 290 insertions(+), 16 deletions(-) create mode 100644 packages/vm/src/wasm_backend/metering.rs diff --git a/Cargo.lock b/Cargo.lock index 7a8130771f..b173efb60c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -683,6 +683,7 @@ dependencies = [ "wasm-encoder 0.205.0", "wasmer", "wasmer-middlewares", + "wasmer-types", "wat", ] diff --git a/contracts/burner/Cargo.lock b/contracts/burner/Cargo.lock index 38c4ed772c..671d8342c0 100644 --- a/contracts/burner/Cargo.lock +++ b/contracts/burner/Cargo.lock @@ -433,6 +433,7 @@ dependencies = [ "tracing", "wasmer", "wasmer-middlewares", + "wasmer-types", ] [[package]] diff --git a/contracts/crypto-verify/Cargo.lock b/contracts/crypto-verify/Cargo.lock index 37b4a32982..a114405978 100644 --- a/contracts/crypto-verify/Cargo.lock +++ b/contracts/crypto-verify/Cargo.lock @@ -428,6 +428,7 @@ dependencies = [ "tracing", "wasmer", "wasmer-middlewares", + "wasmer-types", ] [[package]] diff --git a/contracts/cyberpunk/Cargo.lock b/contracts/cyberpunk/Cargo.lock index 5c1ed5028f..0fb51ac8fd 100644 --- a/contracts/cyberpunk/Cargo.lock +++ b/contracts/cyberpunk/Cargo.lock @@ -457,6 +457,7 @@ dependencies = [ "tracing", "wasmer", "wasmer-middlewares", + "wasmer-types", ] [[package]] diff --git a/contracts/empty/Cargo.lock b/contracts/empty/Cargo.lock index 06de5d3f33..32caae9106 100644 --- a/contracts/empty/Cargo.lock +++ b/contracts/empty/Cargo.lock @@ -422,6 +422,7 @@ dependencies = [ "tracing", "wasmer", "wasmer-middlewares", + "wasmer-types", ] [[package]] diff --git a/contracts/floaty/Cargo.lock b/contracts/floaty/Cargo.lock index bf322a60ec..2eb79d30cf 100644 --- a/contracts/floaty/Cargo.lock +++ b/contracts/floaty/Cargo.lock @@ -422,6 +422,7 @@ dependencies = [ "tracing", "wasmer", "wasmer-middlewares", + "wasmer-types", ] [[package]] diff --git a/contracts/hackatom/Cargo.lock b/contracts/hackatom/Cargo.lock index cd23e7adf7..de00e6ccf7 100644 --- a/contracts/hackatom/Cargo.lock +++ b/contracts/hackatom/Cargo.lock @@ -422,6 +422,7 @@ dependencies = [ "tracing", "wasmer", "wasmer-middlewares", + "wasmer-types", ] [[package]] diff --git a/contracts/ibc-callbacks/Cargo.lock b/contracts/ibc-callbacks/Cargo.lock index 1bcd13230e..79d0b6660e 100644 --- a/contracts/ibc-callbacks/Cargo.lock +++ b/contracts/ibc-callbacks/Cargo.lock @@ -422,6 +422,7 @@ dependencies = [ "tracing", "wasmer", "wasmer-middlewares", + "wasmer-types", ] [[package]] diff --git a/contracts/ibc-reflect-send/Cargo.lock b/contracts/ibc-reflect-send/Cargo.lock index ef72b1da85..f3d7b71438 100644 --- a/contracts/ibc-reflect-send/Cargo.lock +++ b/contracts/ibc-reflect-send/Cargo.lock @@ -422,6 +422,7 @@ dependencies = [ "tracing", "wasmer", "wasmer-middlewares", + "wasmer-types", ] [[package]] diff --git a/contracts/ibc-reflect/Cargo.lock b/contracts/ibc-reflect/Cargo.lock index fc217198a7..2d50eb7b3e 100644 --- a/contracts/ibc-reflect/Cargo.lock +++ b/contracts/ibc-reflect/Cargo.lock @@ -422,6 +422,7 @@ dependencies = [ "tracing", "wasmer", "wasmer-middlewares", + "wasmer-types", ] [[package]] diff --git a/contracts/queue/Cargo.lock b/contracts/queue/Cargo.lock index 7ff894faab..b30591b185 100644 --- a/contracts/queue/Cargo.lock +++ b/contracts/queue/Cargo.lock @@ -422,6 +422,7 @@ dependencies = [ "tracing", "wasmer", "wasmer-middlewares", + "wasmer-types", ] [[package]] diff --git a/contracts/reflect/Cargo.lock b/contracts/reflect/Cargo.lock index da98d89470..3605b6457c 100644 --- a/contracts/reflect/Cargo.lock +++ b/contracts/reflect/Cargo.lock @@ -422,6 +422,7 @@ dependencies = [ "tracing", "wasmer", "wasmer-middlewares", + "wasmer-types", ] [[package]] diff --git a/contracts/staking/Cargo.lock b/contracts/staking/Cargo.lock index e861b97993..b4e9546197 100644 --- a/contracts/staking/Cargo.lock +++ b/contracts/staking/Cargo.lock @@ -422,6 +422,7 @@ dependencies = [ "tracing", "wasmer", "wasmer-middlewares", + "wasmer-types", ] [[package]] diff --git a/contracts/virus/Cargo.lock b/contracts/virus/Cargo.lock index 8cc2706db2..fe8fb0e14d 100644 --- a/contracts/virus/Cargo.lock +++ b/contracts/virus/Cargo.lock @@ -422,6 +422,7 @@ dependencies = [ "tracing", "wasmer", "wasmer-middlewares", + "wasmer-types", ] [[package]] diff --git a/packages/vm/Cargo.toml b/packages/vm/Cargo.toml index 367806162a..b83e6ef6af 100644 --- a/packages/vm/Cargo.toml +++ b/packages/vm/Cargo.toml @@ -62,6 +62,7 @@ wasmer = { version = "=4.3.7", default-features = false, features = [ "singlepass", ] } wasmer-middlewares = "=4.3.7" +wasmer-types = "=4.3.7" strum = { version = "0.26.2", default-features = false, features = ["derive"] } # For heap profiling. Only used in the "heap_profiling" example. This has to be a non-dev dependency # because cargo currently does not support optional dev-dependencies. diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs index f3f65204cd..caa7322900 100644 --- a/packages/vm/src/instance.rs +++ b/packages/vm/src/instance.rs @@ -914,7 +914,7 @@ mod tests { let report2 = instance.create_gas_report(); assert_eq!(report2.used_externally, 251); - assert_eq!(report2.used_internally, 17457465); + assert_eq!(report2.used_internally, 21589990); assert_eq!(report2.limit, LIMIT); assert_eq!( report2.remaining, @@ -1106,7 +1106,7 @@ mod tests { .unwrap(); let init_used = orig_gas - instance.get_gas_left(); - assert_eq!(init_used, 17457716); + assert_eq!(init_used, 21590241); } #[test] @@ -1131,7 +1131,7 @@ mod tests { .unwrap(); let execute_used = gas_before_execute - instance.get_gas_left(); - assert_eq!(execute_used, 21041196); + assert_eq!(execute_used, 26961511); } #[test] @@ -1174,6 +1174,6 @@ mod tests { ); let query_used = gas_before_query - instance.get_gas_left(); - assert_eq!(query_used, 12631261); + assert_eq!(query_used, 15938086); } } diff --git a/packages/vm/src/wasm_backend/engine.rs b/packages/vm/src/wasm_backend/engine.rs index 192cfb8bf8..78bcb859ef 100644 --- a/packages/vm/src/wasm_backend/engine.rs +++ b/packages/vm/src/wasm_backend/engine.rs @@ -3,12 +3,12 @@ use wasmer::NativeEngineExt; use wasmer::{ sys::BaseTunables, wasmparser::Operator, CompilerConfig, Engine, Pages, Target, WASM_PAGE_SIZE, }; -use wasmer_middlewares::Metering; use crate::size::Size; use super::gatekeeper::Gatekeeper; use super::limiting_tunables::LimitingTunables; +use super::metering::{is_accounting, Metering}; /// WebAssembly linear memory objects have sizes measured in pages. Each page /// is 65536 (2^16) bytes. In WebAssembly version 1, a linear memory can have at @@ -29,17 +29,10 @@ fn cost(operator: &Operator) -> u64 { // compared to newly compiled ones. const GAS_PER_OPERATION: u64 = 115; - match operator { - Operator::Loop { .. } - | Operator::End - | Operator::Else - | Operator::Br { .. } - | Operator::BrTable { .. } - | Operator::BrIf { .. } - | Operator::Call { .. } - | Operator::CallIndirect { .. } - | Operator::Return => GAS_PER_OPERATION * 14, - _ => GAS_PER_OPERATION, + if is_accounting(operator) { + GAS_PER_OPERATION * 14 + } else { + GAS_PER_OPERATION } } diff --git a/packages/vm/src/wasm_backend/metering.rs b/packages/vm/src/wasm_backend/metering.rs new file mode 100644 index 0000000000..0155b88687 --- /dev/null +++ b/packages/vm/src/wasm_backend/metering.rs @@ -0,0 +1,265 @@ +use std::fmt; +use std::sync::{Arc, Mutex}; +use wasmer::wasmparser::{BlockType as WpTypeOrFuncType, Operator}; +use wasmer::{ + ExportIndex, FunctionMiddleware, GlobalInit, GlobalType, LocalFunctionIndex, MiddlewareError, + MiddlewareReaderState, ModuleMiddleware, Mutability, Type, +}; +use wasmer_types::{GlobalIndex, ModuleInfo}; + +#[derive(Clone)] +struct MeteringGlobalIndexes(GlobalIndex, GlobalIndex); + +impl MeteringGlobalIndexes { + /// The global index in the current module for remaining points. + fn remaining_points(&self) -> GlobalIndex { + self.0 + } + + /// The global index in the current module for a boolean indicating whether points are exhausted + /// or not. + /// This boolean is represented as a i32 global: + /// * 0: there are remaining points + /// * 1: points have been exhausted + fn points_exhausted(&self) -> GlobalIndex { + self.1 + } +} + +impl fmt::Debug for MeteringGlobalIndexes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("MeteringGlobalIndexes") + .field("remaining_points", &self.remaining_points()) + .field("points_exhausted", &self.points_exhausted()) + .finish() + } +} + +/// The module-level metering middleware. +/// +/// # Panic +/// +/// An instance of `Metering` should _not_ be shared among different +/// modules, since it tracks module-specific information like the +/// global index to store metering state. Attempts to use a `Metering` +/// instance from multiple modules will result in a panic. +/// +/// # Example +/// +/// ```rust +/// use std::sync::Arc; +/// use wasmer::{wasmparser::Operator, CompilerConfig}; +/// use wasmer_middlewares::Metering; +/// +/// fn create_metering_middleware(compiler_config: &mut dyn CompilerConfig) { +/// // Let's define a dummy cost function, +/// // which counts 1 for all operators. +/// let cost_function = |_operator: &Operator| -> u64 { 1 }; +/// +/// // Let's define the initial limit. +/// let initial_limit = 10; +/// +/// // Let's creating the metering middleware. +/// let metering = Arc::new(Metering::new( +/// initial_limit, +/// cost_function +/// )); +/// +/// // Finally, let's push the middleware. +/// compiler_config.push_middleware(metering); +/// } +/// ``` +pub struct Metering u64 + Send + Sync> { + /// Initial limit of points. + initial_limit: u64, + + /// Function that maps each operator to a cost in "points". + cost_function: Arc, + + /// The global indexes for metering points. + global_indexes: Mutex>, +} + +/// The function-level metering middleware. +pub struct FunctionMetering u64 + Send + Sync> { + /// Function that maps each operator to a cost in "points". + cost_function: Arc, + + /// The global indexes for metering points. + global_indexes: MeteringGlobalIndexes, + + /// Accumulated cost of the current basic block. + accumulated_cost: u64, +} + +impl u64 + Send + Sync> Metering { + /// Creates a `Metering` middleware. + pub fn new(initial_limit: u64, cost_function: F) -> Self { + Self { + initial_limit, + cost_function: Arc::new(cost_function), + global_indexes: Mutex::new(None), + } + } +} + +/// Returns `true` if and only if the given operator is an accounting operator. +/// Accounting operators do additional work to track the metering points. +pub fn is_accounting(operator: &Operator) -> bool { + matches!( + operator, + Operator::Loop { .. } // loop headers are branch targets + | Operator::End // block ends are branch targets + | Operator::If { .. } // branch source, "if" can branch to else branch + | Operator::Else // "else" is the "end" of an if branch + | Operator::Br { .. } // branch source + | Operator::BrTable { .. } // branch source + | Operator::BrIf { .. } // branch source + | Operator::Call { .. } // function call - branch source + | Operator::CallIndirect { .. } // function call - branch source + | Operator::Return // end of function - branch source + // exceptions proposal + | Operator::Throw { .. } // branch source + | Operator::ThrowRef // branch source + | Operator::Rethrow { .. } // branch source + | Operator::Delegate { .. } // branch source + | Operator::Catch { .. } // branch target + // tail_call proposal + | Operator::ReturnCall { .. } // branch source + | Operator::ReturnCallIndirect { .. } // branch source + // gc proposal + | Operator::BrOnCast { .. } // branch source + | Operator::BrOnCastFail { .. } // branch source + // function_references proposal + | Operator::CallRef { .. } // branch source + | Operator::ReturnCallRef { .. } // branch source + | Operator::BrOnNull { .. } // branch source + | Operator::BrOnNonNull { .. } // branch source + ) +} + +impl u64 + Send + Sync> fmt::Debug for Metering { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Metering") + .field("initial_limit", &self.initial_limit) + .field("cost_function", &"") + .field("global_indexes", &self.global_indexes) + .finish() + } +} + +impl u64 + Send + Sync + 'static> ModuleMiddleware for Metering { + /// Generates a `FunctionMiddleware` for a given function. + fn generate_function_middleware(&self, _: LocalFunctionIndex) -> Box { + Box::new(FunctionMetering { + cost_function: self.cost_function.clone(), + global_indexes: self.global_indexes.lock().unwrap().clone().unwrap(), + accumulated_cost: 0, + }) + } + + /// Transforms a `ModuleInfo` struct in-place. This is called before application on functions begins. + fn transform_module_info(&self, module_info: &mut ModuleInfo) -> Result<(), MiddlewareError> { + let mut global_indexes = self.global_indexes.lock().unwrap(); + + if global_indexes.is_some() { + panic!("Metering::transform_module_info: Attempting to use a `Metering` middleware from multiple modules."); + } + + // Append a global for remaining points and initialize it. + let remaining_points_global_index = module_info + .globals + .push(GlobalType::new(Type::I64, Mutability::Var)); + + module_info + .global_initializers + .push(GlobalInit::I64Const(self.initial_limit as i64)); + + module_info.exports.insert( + "wasmer_metering_remaining_points".to_string(), + ExportIndex::Global(remaining_points_global_index), + ); + + // Append a global for the exhausted points boolean and initialize it. + let points_exhausted_global_index = module_info + .globals + .push(GlobalType::new(Type::I32, Mutability::Var)); + + module_info + .global_initializers + .push(GlobalInit::I32Const(0)); + + module_info.exports.insert( + "wasmer_metering_points_exhausted".to_string(), + ExportIndex::Global(points_exhausted_global_index), + ); + + *global_indexes = Some(MeteringGlobalIndexes( + remaining_points_global_index, + points_exhausted_global_index, + )); + + Ok(()) + } +} + +impl u64 + Send + Sync> fmt::Debug for FunctionMetering { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FunctionMetering") + .field("cost_function", &"") + .field("global_indexes", &self.global_indexes) + .finish() + } +} + +impl u64 + Send + Sync> FunctionMiddleware for FunctionMetering { + fn feed<'a>( + &mut self, + operator: Operator<'a>, + state: &mut MiddlewareReaderState<'a>, + ) -> Result<(), MiddlewareError> { + // Get the cost of the current operator, and add it to the accumulator. + // This needs to be done before the metering logic, to prevent operators like `Call` from escaping metering in some + // corner cases. + self.accumulated_cost += (self.cost_function)(&operator); + + // Possible sources and targets of a branch. Finalize the cost of the previous basic block and perform necessary checks. + if is_accounting(&operator) && self.accumulated_cost > 0 { + state.extend(&[ + // if unsigned(globals[remaining_points_index]) < unsigned(self.accumulated_cost) { throw(); } + Operator::GlobalGet { + global_index: self.global_indexes.remaining_points().as_u32(), + }, + Operator::I64Const { + value: self.accumulated_cost as i64, + }, + Operator::I64LtU, + Operator::If { + blockty: WpTypeOrFuncType::Empty, + }, + Operator::I32Const { value: 1 }, + Operator::GlobalSet { + global_index: self.global_indexes.points_exhausted().as_u32(), + }, + Operator::Unreachable, + Operator::End, + // globals[remaining_points_index] -= self.accumulated_cost; + Operator::GlobalGet { + global_index: self.global_indexes.remaining_points().as_u32(), + }, + Operator::I64Const { + value: self.accumulated_cost as i64, + }, + Operator::I64Sub, + Operator::GlobalSet { + global_index: self.global_indexes.remaining_points().as_u32(), + }, + ]); + + self.accumulated_cost = 0; + } + state.push_operator(operator); + + Ok(()) + } +} diff --git a/packages/vm/src/wasm_backend/mod.rs b/packages/vm/src/wasm_backend/mod.rs index a7c8bf0570..1c330dc120 100644 --- a/packages/vm/src/wasm_backend/mod.rs +++ b/packages/vm/src/wasm_backend/mod.rs @@ -2,6 +2,7 @@ mod compile; mod engine; mod gatekeeper; mod limiting_tunables; +mod metering; #[cfg(test)] pub use engine::make_compiler_config; From cf413c5ad6a58a87e0be894584f01506f3b2e0af Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Mon, 9 Sep 2024 13:43:34 +0200 Subject: [PATCH 2/2] Fix --- packages/vm/src/errors/communication_error.rs | 7 ++ packages/vm/src/imports.rs | 6 +- packages/vm/src/sections.rs | 69 ++++++++++++------- 3 files changed, 53 insertions(+), 29 deletions(-) diff --git a/packages/vm/src/errors/communication_error.rs b/packages/vm/src/errors/communication_error.rs index 73a793a528..b9c4e60e34 100644 --- a/packages/vm/src/errors/communication_error.rs +++ b/packages/vm/src/errors/communication_error.rs @@ -25,6 +25,9 @@ pub enum CommunicationError { #[from] source: RegionValidationError, }, + /// When the contract supplies invalid section data to the host. See also `decode_sections` [crate::sections::decode_sections]. + #[error("Got an invalid section: {}", msg)] + InvalidSection { msg: String }, /// Whenever UTF-8 bytes cannot be decoded into a unicode string, e.g. in String::from_utf8 or str::from_utf8. #[error("Cannot decode UTF8 bytes into string: {}", msg)] InvalidUtf8 { msg: String }, @@ -56,6 +59,10 @@ impl CommunicationError { CommunicationError::InvalidOrder { value } } + pub(crate) fn invalid_section(msg: impl Into) -> Self { + CommunicationError::InvalidSection { msg: msg.into() } + } + #[allow(dead_code)] pub(crate) fn invalid_utf8(msg: impl ToString) -> Self { CommunicationError::InvalidUtf8 { diff --git a/packages/vm/src/imports.rs b/packages/vm/src/imports.rs index 581e47b8b1..dc8ee955f9 100644 --- a/packages/vm/src/imports.rs +++ b/packages/vm/src/imports.rs @@ -730,9 +730,9 @@ pub fn do_ed25519_batch_verify< (EDDSA_PUBKEY_LEN + 4) * MAX_COUNT_ED25519_BATCH, )?; - let messages = decode_sections(&messages); - let signatures = decode_sections(&signatures); - let public_keys = decode_sections(&public_keys); + let messages = decode_sections(&messages)?; + let signatures = decode_sections(&signatures)?; + let public_keys = decode_sections(&public_keys)?; let gas_cost = if public_keys.len() == 1 { &data.gas_config.ed25519_batch_verify_one_pubkey_cost diff --git a/packages/vm/src/sections.rs b/packages/vm/src/sections.rs index c0c40166a5..b1a95cbe85 100644 --- a/packages/vm/src/sections.rs +++ b/packages/vm/src/sections.rs @@ -1,13 +1,12 @@ use crate::conversion::to_u32; -use crate::errors::VmResult; +use crate::{CommunicationError, VmResult}; /// Decodes sections of data into multiple slices. /// /// Each encoded section is suffixed by a section length, encoded as big endian uint32. /// -/// See also: `encode_section`. -#[allow(dead_code)] -pub fn decode_sections(data: &[u8]) -> Vec<&[u8]> { +/// See also: [`encode_sections`]. +pub fn decode_sections(data: &[u8]) -> Result, CommunicationError> { let mut result: Vec<&[u8]> = vec![]; let mut remaining_len = data.len(); while remaining_len >= 4 { @@ -17,11 +16,20 @@ pub fn decode_sections(data: &[u8]) -> Vec<&[u8]> { data[remaining_len - 2], data[remaining_len - 1], ]) as usize; - result.push(&data[remaining_len - 4 - tail_len..remaining_len - 4]); - remaining_len -= 4 + tail_len; + let tail_len_idx = remaining_len - 4; // index of the first byte of the tail length + let section_start = tail_len_idx + .checked_sub(tail_len) + .ok_or_else(|| CommunicationError::invalid_section("section length overflow"))?; + result.push(&data[section_start..tail_len_idx]); + remaining_len = section_start; + } + if remaining_len > 0 { + return Err(CommunicationError::invalid_section( + "extra data outside of any section", + )); } result.reverse(); - result + Ok(result) } /// Encodes multiple sections of data into one vector. @@ -57,52 +65,61 @@ mod tests { #[test] fn decode_sections_works_for_empty_sections() { - let dec = decode_sections(&[]); + let dec = decode_sections(&[]).unwrap(); assert_eq!(dec.len(), 0); - let dec = decode_sections(b"\0\0\0\0"); + let dec = decode_sections(b"\0\0\0\0").unwrap(); assert_eq!(dec, &[&[0u8; 0]]); - let dec = decode_sections(b"\0\0\0\0\0\0\0\0"); + let dec = decode_sections(b"\0\0\0\0\0\0\0\0").unwrap(); assert_eq!(dec, &[&[0u8; 0]; 2]); - let dec = decode_sections(b"\0\0\0\0\0\0\0\0\0\0\0\0"); + let dec = decode_sections(b"\0\0\0\0\0\0\0\0\0\0\0\0").unwrap(); assert_eq!(dec, &[&[0u8; 0]; 3]); - // ignores "trailing" stuff - let dec = decode_sections(b"\0\0\0\0\0\0\0\0\0\0\0"); - assert_eq!(dec, &[&[0u8; 0]; 2]); } #[test] fn decode_sections_works_for_one_element() { - let dec = decode_sections(b"\xAA\0\0\0\x01"); + let dec = decode_sections(b"\xAA\0\0\0\x01").unwrap(); assert_eq!(dec, &[vec![0xAA]]); - let dec = decode_sections(b"\xAA\xBB\0\0\0\x02"); + let dec = decode_sections(b"\xAA\xBB\0\0\0\x02").unwrap(); assert_eq!(dec, &[vec![0xAA, 0xBB]]); - let dec = decode_sections(b"\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\0\0\x01\x15"); + let dec = decode_sections(b"\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\0\0\x01\x15").unwrap(); assert_eq!(dec, &[vec![0x9D; 277]]); } #[test] fn decode_sections_works_for_two_elements() { let data = b"\xAA\0\0\0\x01\xBB\xCC\0\0\0\x02".to_vec(); - assert_eq!(decode_sections(&data), &[vec![0xAA], vec![0xBB, 0xCC]]); + assert_eq!( + decode_sections(&data).unwrap(), + &[vec![0xAA], vec![0xBB, 0xCC]] + ); let data = b"\xDE\xEF\x62\0\0\0\x03\0\0\0\0".to_vec(); - assert_eq!(decode_sections(&data), &[vec![0xDE, 0xEF, 0x62], vec![]]); + assert_eq!( + decode_sections(&data).unwrap(), + &[vec![0xDE, 0xEF, 0x62], vec![]] + ); let data = b"\0\0\0\0\xDE\xEF\x62\0\0\0\x03".to_vec(); - assert_eq!(decode_sections(&data), &[vec![], vec![0xDE, 0xEF, 0x62]]); + assert_eq!( + decode_sections(&data).unwrap(), + &[vec![], vec![0xDE, 0xEF, 0x62]] + ); let data = b"\0\0\0\0\0\0\0\0".to_vec(); - assert_eq!(decode_sections(&data), &[vec![0u8; 0], vec![]]); + assert_eq!(decode_sections(&data).unwrap(), &[vec![0u8; 0], vec![]]); let data = b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\0\0\0\x13\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\x9D\0\0\x01\x15".to_vec(); - assert_eq!(decode_sections(&data), &[vec![0xFF; 19], vec![0x9D; 277]]); + assert_eq!( + decode_sections(&data).unwrap(), + &[vec![0xFF; 19], vec![0x9D; 277]] + ); } #[test] fn decode_sections_works_for_multiple_elements() { - let dec = decode_sections(b"\xAA\0\0\0\x01"); + let dec = decode_sections(b"\xAA\0\0\0\x01").unwrap(); assert_eq!(dec, &[vec![0xAA]]); - let dec = decode_sections(b"\xAA\0\0\0\x01\xDE\xDE\0\0\0\x02"); + let dec = decode_sections(b"\xAA\0\0\0\x01\xDE\xDE\0\0\0\x02").unwrap(); assert_eq!(dec, &[vec![0xAA], vec![0xDE, 0xDE]]); - let dec = decode_sections(b"\xAA\0\0\0\x01\xDE\xDE\0\0\0\x02\0\0\0\0"); + let dec = decode_sections(b"\xAA\0\0\0\x01\xDE\xDE\0\0\0\x02\0\0\0\0").unwrap(); assert_eq!(dec, &[vec![0xAA], vec![0xDE, 0xDE], vec![]]); - let dec = decode_sections(b"\xAA\0\0\0\x01\xDE\xDE\0\0\0\x02\0\0\0\0\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\0\0\0\x13"); + let dec = decode_sections(b"\xAA\0\0\0\x01\xDE\xDE\0\0\0\x02\0\0\0\0\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\0\0\0\x13").unwrap(); assert_eq!(dec, &[vec![0xAA], vec![0xDE, 0xDE], vec![], vec![0xFF; 19]]); }