From dc0efed9d0fc6d68be8c0f644687558b2be29e6f Mon Sep 17 00:00:00 2001 From: Xuejie Xiao Date: Thu, 26 Nov 2020 10:28:48 +0800 Subject: [PATCH] Initial commit --- .gitignore | 2 + Cargo.toml | 20 ++ README.md | 5 + include/api.h | 36 ++++ src/constants.rs | 43 ++++ src/lib.rs | 534 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 640 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 include/api.h create mode 100644 src/constants.rs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..40de92d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "ckb-x64-simulator" +description = "A simulator that allows running CKB smart contracts on x64 environment for tooling benefits" +version = "0.1.0" +license = "MIT" +authors = ["Nervos Core Dev "] +edition = "2018" + +[lib] +crate-type = ["lib", "staticlib", "cdylib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ckb-standalone-debugger = { git = "https://github.com/xxuejie/ckb-standalone-debugger", rev = "7c62220" } +ckb-types = { git = "https://github.com/nervosnetwork/ckb", rev = "79d0cc3" } +lazy_static = "1.4" +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..6045f3b --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# ckb-x64-simulator + +ckb-x64-simulator provides a simulator environment, which can be used to compile CKB smart contracts to native x64 environment. The result here, is that all the existing toolings on x64 environment, such as valgrind, address sanitizer, undefined behavior sanitizer, code coverage tools, etc. can be used to ensure the security of smart contracts. One day we might reach the point that RISC-V based toolings have caught up, so this simulator can be sunset, but at the moment now, it provides a good tradeoff to boost smart contract security. + +While this simulator is written in pure Rust, C based APIs are exposed so it can also be linked against a C based smart contract. diff --git a/include/api.h b/include/api.h new file mode 100644 index 0000000..43d8cbf --- /dev/null +++ b/include/api.h @@ -0,0 +1,36 @@ +#ifndef CKB_X64_SIMULATOR_API_H_ +#define CKB_X64_SIMULATOR_API_H_ + +#include +#include + +int ckb_exit(int8_t code); +int ckb_load_tx_hash(void* addr, uint64_t* len, size_t offset); +int ckb_load_transaction(void* addr, uint64_t* len, size_t offset); +int ckb_load_script_hash(void* addr, uint64_t* len, size_t offset); +int ckb_load_script(void* addr, uint64_t* len, size_t offset); +int ckb_debug(const char* s); + +int ckb_load_cell(void* addr, uint64_t* len, size_t offset, size_t index, + size_t source); +int ckb_load_input(void* addr, uint64_t* len, size_t offset, size_t index, + size_t source); +int ckb_load_header(void* addr, uint64_t* len, size_t offset, size_t index, + size_t source); +int ckb_load_witness(void* addr, uint64_t* len, size_t offset, size_t index, + size_t source); +int ckb_load_cell_by_field(void* addr, uint64_t* len, size_t offset, + size_t index, size_t source, size_t field); +int ckb_load_header_by_field(void* addr, uint64_t* len, size_t offset, + size_t index, size_t source, size_t field); +int ckb_load_input_by_field(void* addr, uint64_t* len, size_t offset, + size_t index, size_t source, size_t field); +int ckb_load_cell_data(void* addr, uint64_t* len, size_t offset, size_t index, + size_t source); + +int ckb_dlopen2(const uint8_t* dep_cell_hash, uint8_t hash_type, + uint8_t* aligned_addr, size_t aligned_size, void** handle, + size_t* consumed_size); +void* ckb_dlsym(void* handle, const char* symbol); + +#endif /* CKB_X64_SIMULATOR_API_H_ */ diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..ddf1401 --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,43 @@ +pub const SYS_EXIT: u64 = 93; +pub const SYS_LOAD_TRANSACTION: u64 = 2051; +pub const SYS_LOAD_SCRIPT: u64 = 2052; +pub const SYS_LOAD_TX_HASH: u64 = 2061; +pub const SYS_LOAD_SCRIPT_HASH: u64 = 2062; +pub const SYS_LOAD_CELL: u64 = 2071; +pub const SYS_LOAD_HEADER: u64 = 2072; +pub const SYS_LOAD_INPUT: u64 = 2073; +pub const SYS_LOAD_WITNESS: u64 = 2074; +pub const SYS_LOAD_CELL_BY_FIELD: u64 = 2081; +pub const SYS_LOAD_HEADER_BY_FIELD: u64 = 2082; +pub const SYS_LOAD_INPUT_BY_FIELD: u64 = 2083; +pub const SYS_LOAD_CELL_DATA_AS_CODE: u64 = 2091; +pub const SYS_LOAD_CELL_DATA: u64 = 2092; +pub const SYS_DEBUG: u64 = 2177; + +pub const CKB_SUCCESS: i32 = 0; +pub const CKB_INDEX_OUT_OF_BOUND: i32 = 1; +pub const CKB_ITEM_MISSING: i32 = 2; + +pub const SOURCE_INPUT: u64 = 1; +pub const SOURCE_OUTPUT: u64 = 2; +pub const SOURCE_CELL_DEP: u64 = 3; +pub const SOURCE_HEADER_DEP: u64 = 4; +pub const SOURCE_GROUP_INPUT: u64 = 0x0100000000000001; +pub const SOURCE_GROUP_OUTPUT: u64 = 0x0100000000000002; +pub const SOURCE_GROUP_CELL_DEP: u64 = 0x0100000000000003; +pub const SOURCE_GROUP_HEADER_DEP: u64 = 0x0100000000000004; + +pub const CELL_FIELD_CAPACITY: u64 = 0; +pub const CELL_FIELD_DATA_HASH: u64 = 1; +pub const CELL_FIELD_LOCK: u64 = 2; +pub const CELL_FIELD_LOCK_HASH: u64 = 3; +pub const CELL_FIELD_TYPE: u64 = 4; +pub const CELL_FIELD_TYPE_HASH: u64 = 5; +pub const CELL_FIELD_OCCUPIED_CAPACITY: u64 = 6; + +pub const HEADER_FIELD_EPOCH_NUMBER: u64 = 0; +pub const HEADER_FIELD_EPOCH_START_BLOCK_NUMBER: u64 = 1; +pub const HEADER_FIELD_EPOCH_LENGTH: u64 = 2; + +pub const INPUT_FIELD_OUT_POINT: u64 = 0; +pub const INPUT_FIELD_SINCE: u64 = 1; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..41fbce1 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,534 @@ +pub mod constants; + +#[macro_use] +extern crate lazy_static; + +use ckb_standalone_debugger::transaction::{MockTransaction, ReprMockTransaction}; +use ckb_types::{ + bytes::Bytes, + core::{cell::CellMetaBuilder, Capacity, HeaderView}, + packed::{self, Byte32, CellInput, CellOutput, Script}, + prelude::{Entity, Unpack}, +}; +use constants::{ + CELL_FIELD_CAPACITY, CELL_FIELD_DATA_HASH, CELL_FIELD_LOCK, CELL_FIELD_LOCK_HASH, + CELL_FIELD_OCCUPIED_CAPACITY, CELL_FIELD_TYPE, CELL_FIELD_TYPE_HASH, CKB_INDEX_OUT_OF_BOUND, + CKB_ITEM_MISSING, CKB_SUCCESS, HEADER_FIELD_EPOCH_LENGTH, HEADER_FIELD_EPOCH_NUMBER, + HEADER_FIELD_EPOCH_START_BLOCK_NUMBER, INPUT_FIELD_OUT_POINT, INPUT_FIELD_SINCE, + SOURCE_CELL_DEP, SOURCE_GROUP_CELL_DEP, SOURCE_GROUP_HEADER_DEP, SOURCE_GROUP_INPUT, + SOURCE_GROUP_OUTPUT, SOURCE_HEADER_DEP, SOURCE_INPUT, SOURCE_OUTPUT, +}; +use serde_derive::{Deserialize, Serialize}; +use std::ffi::CStr; +use std::os::raw::{c_char, c_int, c_void}; + +#[derive(Clone, Serialize, Deserialize)] +pub struct RunningSetup { + pub is_lock_script: bool, + pub is_output: bool, + pub script_index: u64, +} + +lazy_static! { + static ref TRANSACTION: MockTransaction = { + let tx_filename = std::env::var("CKB_TX_FILE").expect("environment variable"); + let tx_content = std::fs::read_to_string(tx_filename).expect("read tx file"); + let repr_mock_tx: ReprMockTransaction = + serde_json::from_str(&tx_content).expect("parse tx file"); + let mock_tx: MockTransaction = repr_mock_tx.into(); + mock_tx + }; + static ref SETUP: RunningSetup = { + let setup_filename = std::env::var("CKB_RUNNING_SETUP").expect("environment variable"); + let setup_content = std::fs::read_to_string(setup_filename).expect("read setup file"); + serde_json::from_str(&setup_content).expect("parse setup file") + }; +} + +#[no_mangle] +pub extern "C" fn ckb_exit(code: i8) -> i32 { + std::process::exit(code.into()); +} + +#[no_mangle] +pub extern "C" fn ckb_load_tx_hash(ptr: *mut c_void, len: *mut u64, offset: u64) -> c_int { + let view = TRANSACTION.tx.clone().into_view(); + store_data(ptr, len, offset, view.hash().as_slice()); + CKB_SUCCESS +} + +#[no_mangle] +pub extern "C" fn ckb_load_transaction(ptr: *mut c_void, len: *mut u64, offset: u64) -> c_int { + store_data(ptr, len, offset, TRANSACTION.tx.as_slice()); + CKB_SUCCESS +} + +#[no_mangle] +pub extern "C" fn ckb_load_script_hash(ptr: *mut c_void, len: *mut u64, offset: u64) -> c_int { + let hash = fetch_current_script().calc_script_hash(); + store_data(ptr, len, offset, hash.as_slice()); + CKB_SUCCESS +} + +#[no_mangle] +pub extern "C" fn ckb_load_script(ptr: *mut c_void, len: *mut u64, offset: u64) -> c_int { + store_data(ptr, len, offset, fetch_current_script().as_slice()); + CKB_SUCCESS +} + +#[no_mangle] +pub extern "C" fn ckb_debug(s: *const c_char) { + let message = unsafe { CStr::from_ptr(s) }.to_str().expect("UTF8 error!"); + println!("Debug message: {}", message); +} + +#[no_mangle] +pub extern "C" fn ckb_load_cell( + ptr: *mut c_void, + len: *mut u64, + offset: u64, + index: u64, + source: u64, +) -> c_int { + let (cell, _) = match fetch_cell(index, source) { + Ok(cell) => cell, + Err(code) => return code, + }; + store_data(ptr, len, offset, cell.as_slice()); + CKB_SUCCESS +} + +#[no_mangle] +pub extern "C" fn ckb_load_input( + ptr: *mut c_void, + len: *mut u64, + offset: u64, + index: u64, + source: u64, +) -> c_int { + let input = match fetch_input(index, source) { + Ok(input) => input, + Err(code) => return code, + }; + store_data(ptr, len, offset, input.as_slice()); + CKB_SUCCESS +} + +#[no_mangle] +pub extern "C" fn ckb_load_header( + ptr: *mut c_void, + len: *mut u64, + offset: u64, + index: u64, + source: u64, +) -> c_int { + let header = match fetch_header(index, source) { + Ok(input) => input, + Err(code) => return code, + }; + store_data(ptr, len, offset, header.data().as_slice()); + CKB_SUCCESS +} + +#[no_mangle] +pub extern "C" fn ckb_load_witness( + ptr: *mut c_void, + len: *mut u64, + offset: u64, + index: u64, + source: u64, +) -> c_int { + let witness = match fetch_witness(index, source) { + Some(witness) => witness, + None => return CKB_INDEX_OUT_OF_BOUND, + }; + store_data(ptr, len, offset, &witness.raw_data()); + CKB_SUCCESS +} + +#[no_mangle] +pub extern "C" fn ckb_load_cell_by_field( + ptr: *mut c_void, + len: *mut u64, + offset: u64, + index: u64, + source: u64, + field: u64, +) -> c_int { + let (cell, cell_data) = match fetch_cell(index, source) { + Ok(cell) => cell, + Err(code) => return code, + }; + let cell_meta = CellMetaBuilder::from_cell_output(cell.clone(), cell_data.clone()).build(); + match field { + CELL_FIELD_CAPACITY => { + let capacity: Capacity = cell.capacity().unpack(); + let data = capacity.as_u64().to_le_bytes(); + store_data(ptr, len, offset, &data[..]); + } + CELL_FIELD_DATA_HASH => { + let hash = CellOutput::calc_data_hash(&cell_data); + store_data(ptr, len, offset, hash.as_slice()); + } + CELL_FIELD_OCCUPIED_CAPACITY => { + let data = cell_meta + .occupied_capacity() + .expect("capacity error") + .as_u64() + .to_le_bytes(); + store_data(ptr, len, offset, &data[..]); + } + CELL_FIELD_LOCK => { + let lock = cell.lock(); + store_data(ptr, len, offset, lock.as_slice()); + } + CELL_FIELD_LOCK_HASH => { + let hash = cell.calc_lock_hash(); + store_data(ptr, len, offset, &hash.as_bytes()); + } + CELL_FIELD_TYPE => match cell.type_().to_opt() { + Some(type_) => { + store_data(ptr, len, offset, type_.as_slice()); + } + None => { + return CKB_ITEM_MISSING; + } + }, + CELL_FIELD_TYPE_HASH => match cell.type_().to_opt() { + Some(type_) => { + let hash = type_.calc_script_hash(); + store_data(ptr, len, offset, &hash.as_bytes()); + } + None => { + return CKB_ITEM_MISSING; + } + }, + _ => panic!("Invalid field: {}", field), + }; + CKB_SUCCESS +} + +#[no_mangle] +pub extern "C" fn ckb_load_header_by_field( + ptr: *mut c_void, + len: *mut u64, + offset: u64, + index: u64, + source: u64, + field: u64, +) -> c_int { + let header = match fetch_header(index, source) { + Ok(input) => input, + Err(code) => return code, + }; + let epoch = header.epoch(); + let value = match field { + HEADER_FIELD_EPOCH_NUMBER => epoch.number(), + HEADER_FIELD_EPOCH_START_BLOCK_NUMBER => header + .number() + .checked_sub(epoch.index()) + .expect("Overflow!"), + HEADER_FIELD_EPOCH_LENGTH => epoch.length(), + _ => panic!("Invalid field: {}", field), + }; + let data = value.to_le_bytes(); + store_data(ptr, len, offset, &data[..]); + CKB_SUCCESS +} + +#[no_mangle] +pub extern "C" fn ckb_load_input_by_field( + ptr: *mut c_void, + len: *mut u64, + offset: u64, + index: u64, + source: u64, + field: u64, +) -> c_int { + let input = match fetch_input(index, source) { + Ok(input) => input, + Err(code) => return code, + }; + match field { + INPUT_FIELD_OUT_POINT => { + store_data(ptr, len, offset, input.previous_output().as_slice()); + } + INPUT_FIELD_SINCE => { + let since: u64 = input.since().unpack(); + let data = since.to_le_bytes(); + store_data(ptr, len, offset, &data[..]); + } + _ => panic!("Invalid field: {}", field), + }; + CKB_SUCCESS +} + +#[no_mangle] +pub extern "C" fn ckb_load_cell_data( + ptr: *mut c_void, + len: *mut u64, + offset: u64, + index: u64, + source: u64, +) -> c_int { + let (_, cell_data) = match fetch_cell(index, source) { + Ok(cell) => cell, + Err(code) => return code, + }; + store_data(ptr, len, offset, &cell_data); + CKB_SUCCESS +} + +fn fetch_cell(index: u64, source: u64) -> Result<(CellOutput, Bytes), c_int> { + match source { + SOURCE_INPUT => TRANSACTION + .mock_info + .inputs + .get(index as usize) + .ok_or(CKB_INDEX_OUT_OF_BOUND) + .map(|input| (input.output.clone(), input.data.clone())), + SOURCE_OUTPUT => TRANSACTION + .tx + .raw() + .outputs() + .get(index as usize) + .ok_or(CKB_INDEX_OUT_OF_BOUND) + .map(|output| { + ( + output, + TRANSACTION + .tx + .raw() + .outputs_data() + .get(index as usize) + .expect("cell data mismatch") + .unpack(), + ) + }), + SOURCE_CELL_DEP => TRANSACTION + .mock_info + .cell_deps + .get(index as usize) + .ok_or(CKB_INDEX_OUT_OF_BOUND) + .map(|cell_dep| (cell_dep.output.clone(), cell_dep.data.clone())), + SOURCE_HEADER_DEP => Err(CKB_INDEX_OUT_OF_BOUND), + SOURCE_GROUP_INPUT => { + let (indices, _) = fetch_group_indices(); + indices + .get(index as usize) + .ok_or(CKB_INDEX_OUT_OF_BOUND) + .and_then(|actual_index| { + TRANSACTION + .mock_info + .inputs + .get(*actual_index) + .ok_or(CKB_INDEX_OUT_OF_BOUND) + .map(|input| (input.output.clone(), input.data.clone())) + }) + } + SOURCE_GROUP_OUTPUT => { + let (_, indices) = fetch_group_indices(); + indices + .get(index as usize) + .ok_or(CKB_INDEX_OUT_OF_BOUND) + .and_then(|actual_index| { + TRANSACTION + .tx + .raw() + .outputs() + .get(*actual_index) + .ok_or(CKB_INDEX_OUT_OF_BOUND) + .map(|output| { + ( + output, + TRANSACTION + .tx + .raw() + .outputs_data() + .get(index as usize) + .expect("cell data mismatch") + .unpack(), + ) + }) + }) + } + SOURCE_GROUP_CELL_DEP => Err(CKB_INDEX_OUT_OF_BOUND), + SOURCE_GROUP_HEADER_DEP => Err(CKB_INDEX_OUT_OF_BOUND), + _ => panic!("Invalid source: {}", source), + } +} + +fn fetch_input(index: u64, source: u64) -> Result { + match source { + SOURCE_INPUT => TRANSACTION + .tx + .raw() + .inputs() + .get(index as usize) + .ok_or(CKB_INDEX_OUT_OF_BOUND), + SOURCE_OUTPUT => Err(CKB_INDEX_OUT_OF_BOUND), + SOURCE_CELL_DEP => Err(CKB_INDEX_OUT_OF_BOUND), + SOURCE_HEADER_DEP => Err(CKB_INDEX_OUT_OF_BOUND), + SOURCE_GROUP_INPUT => { + let (indices, _) = fetch_group_indices(); + indices + .get(index as usize) + .ok_or(CKB_INDEX_OUT_OF_BOUND) + .and_then(|actual_index| { + TRANSACTION + .tx + .raw() + .inputs() + .get(*actual_index) + .ok_or(CKB_INDEX_OUT_OF_BOUND) + }) + } + SOURCE_GROUP_OUTPUT => Err(CKB_INDEX_OUT_OF_BOUND), + SOURCE_GROUP_CELL_DEP => Err(CKB_INDEX_OUT_OF_BOUND), + SOURCE_GROUP_HEADER_DEP => Err(CKB_INDEX_OUT_OF_BOUND), + _ => panic!("Invalid source: {}", source), + } +} + +fn find_header(hash: Byte32) -> Option { + TRANSACTION + .mock_info + .header_deps + .iter() + .find(|header| header.hash() == hash) + .cloned() +} + +fn fetch_header(index: u64, source: u64) -> Result { + match source { + SOURCE_INPUT => TRANSACTION + .mock_info + .inputs + .get(index as usize) + .and_then(|input| input.header.as_ref().cloned()) + .ok_or(CKB_INDEX_OUT_OF_BOUND) + .and_then(|header_hash| find_header(header_hash).ok_or(CKB_ITEM_MISSING)), + SOURCE_OUTPUT => Err(CKB_INDEX_OUT_OF_BOUND), + SOURCE_CELL_DEP => TRANSACTION + .mock_info + .cell_deps + .get(index as usize) + .and_then(|cell_dep| cell_dep.header.as_ref().cloned()) + .ok_or(CKB_INDEX_OUT_OF_BOUND) + .and_then(|header_hash| find_header(header_hash).ok_or(CKB_ITEM_MISSING)), + SOURCE_HEADER_DEP => TRANSACTION + .mock_info + .header_deps + .get(index as usize) + .cloned() + .ok_or(CKB_INDEX_OUT_OF_BOUND), + SOURCE_GROUP_INPUT => { + let (indices, _) = fetch_group_indices(); + indices + .get(index as usize) + .ok_or(CKB_INDEX_OUT_OF_BOUND) + .and_then(|actual_index| { + TRANSACTION + .mock_info + .inputs + .get(*actual_index) + .and_then(|input| input.header.as_ref().cloned()) + .ok_or(CKB_INDEX_OUT_OF_BOUND) + .and_then(|header_hash| find_header(header_hash).ok_or(CKB_ITEM_MISSING)) + }) + } + SOURCE_GROUP_OUTPUT => Err(CKB_INDEX_OUT_OF_BOUND), + SOURCE_GROUP_CELL_DEP => Err(CKB_INDEX_OUT_OF_BOUND), + SOURCE_GROUP_HEADER_DEP => Err(CKB_INDEX_OUT_OF_BOUND), + _ => panic!("Invalid source: {}", source), + } +} + +fn fetch_witness(index: u64, source: u64) -> Option { + match source { + SOURCE_INPUT => TRANSACTION.tx.witnesses().get(index as usize), + SOURCE_OUTPUT => TRANSACTION.tx.witnesses().get(index as usize), + SOURCE_GROUP_INPUT => { + let (indices, _) = fetch_group_indices(); + indices + .get(index as usize) + .and_then(|actual_index| TRANSACTION.tx.witnesses().get(*actual_index)) + } + SOURCE_GROUP_OUTPUT => { + let (_, indices) = fetch_group_indices(); + indices + .get(index as usize) + .and_then(|actual_index| TRANSACTION.tx.witnesses().get(*actual_index)) + } + SOURCE_CELL_DEP => None, + SOURCE_HEADER_DEP => None, + SOURCE_GROUP_CELL_DEP => None, + SOURCE_GROUP_HEADER_DEP => None, + _ => panic!("Invalid source: {}", source), + } +} + +fn fetch_group_indices() -> (Vec, Vec) { + let mut input_indices: Vec = vec![]; + let mut output_indices: Vec = vec![]; + let current_script = fetch_current_script(); + + for (i, input) in TRANSACTION.mock_info.inputs.iter().enumerate() { + if SETUP.is_lock_script { + if input.output.lock() == current_script { + input_indices.push(i); + } + } else if let Some(t) = input.output.type_().to_opt() { + if t == current_script { + input_indices.push(i); + } + } + } + for (i, output) in TRANSACTION.tx.raw().outputs().into_iter().enumerate() { + if let Some(t) = output.type_().to_opt() { + if t == current_script { + output_indices.push(i); + } + } + } + (input_indices, output_indices) +} + +fn fetch_current_script() -> Script { + let cell = if SETUP.is_output { + TRANSACTION + .tx + .raw() + .outputs() + .get(SETUP.script_index as usize) + .expect("running script index out of bound!") + } else { + TRANSACTION + .mock_info + .inputs + .get(SETUP.script_index as usize) + .expect("running script index out of bound!") + .output + .clone() + }; + if SETUP.is_lock_script { + cell.lock() + } else { + cell.type_().to_opt().unwrap() + } +} + +fn store_data(ptr: *mut c_void, len: *mut u64, offset: u64, data: &[u8]) { + let size_ptr = unsafe { len.as_mut().expect("casting pointer") }; + let size = *size_ptr; + let buffer = unsafe { + let ptr = (ptr as *mut u8).as_mut().expect("casting pointer"); + std::slice::from_raw_parts_mut(ptr, size as usize) + }; + let data_len = data.len() as u64; + let offset = std::cmp::min(data_len, offset); + let full_size = data_len - offset; + let real_size = std::cmp::min(size, full_size); + *size_ptr = full_size; + buffer.copy_from_slice(&data[offset as usize..(offset + real_size) as usize]); +}