forked from solana-labs/solana
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implements new encoding and adds attribute enum.
- Loading branch information
Showing
2 changed files
with
294 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,293 @@ | ||
use byteorder::{ByteOrder, LittleEndian}; | ||
use std::mem::size_of; | ||
|
||
// Encoding | ||
// | ||
// struct Map { | ||
// number_of_entries: u16, | ||
// attribute: [u16; number_of_entries], | ||
// value_offset: [u32; number_of_entries], | ||
// values: [Value], | ||
// } | ||
// | ||
// struct FatPtr { | ||
// address: u64, | ||
// length: u32, | ||
// } | ||
// | ||
// union Value { | ||
// Direct([u8]), | ||
// Indirect([FatPtr]), | ||
// } | ||
|
||
pub const INDIRECT_FATPTR_LENGTH: usize = 12; | ||
|
||
pub fn predict_header_size(number_of_entries: usize) -> usize { | ||
size_of::<u16>() + number_of_entries * (size_of::<u16>() + size_of::<u32>()) | ||
} | ||
|
||
pub fn get_number_of_entries(encoded: &[u8]) -> usize { | ||
LittleEndian::read_u16(encoded) as usize | ||
} | ||
|
||
pub fn get_attribute(encoded: &[u8], entry_index: usize) -> Attribute { | ||
let attribute_id = | ||
LittleEndian::read_u16(&encoded[size_of::<u16>() + entry_index * size_of::<u16>()..]) as u8; | ||
unsafe { std::mem::transmute(attribute_id) } | ||
} | ||
|
||
pub fn resolve_attribute(encoded: &[u8], attribute: Attribute) -> (usize, usize) { | ||
let number_of_entries = get_number_of_entries(encoded); | ||
let mut entry_index = 1; | ||
while entry_index <= number_of_entries { | ||
entry_index = | ||
(entry_index << 1) + (get_attribute(encoded, entry_index - 1) <= attribute) as usize; | ||
} | ||
entry_index >>= entry_index.trailing_zeros() + 1; | ||
assert!(entry_index > 0 && entry_index <= number_of_entries); | ||
entry_index -= 1; | ||
assert_eq!(get_attribute(encoded, entry_index), attribute); | ||
(entry_index, number_of_entries) | ||
} | ||
|
||
pub fn get_value_start_offset( | ||
encoded: &[u8], | ||
entry_index: usize, | ||
number_of_entries: usize, | ||
) -> usize { | ||
LittleEndian::read_u32( | ||
&encoded[(number_of_entries + 1) * size_of::<u16>() + entry_index * size_of::<u32>()..], | ||
) as usize | ||
} | ||
|
||
pub fn get_value_end_offset(encoded: &[u8], entry_index: usize, number_of_entries: usize) -> usize { | ||
if entry_index + 1 < number_of_entries { | ||
LittleEndian::read_u32( | ||
&encoded[(number_of_entries + 1) * size_of::<u16>() | ||
+ (entry_index + 1) * size_of::<u32>()..], | ||
) as usize | ||
} else { | ||
encoded.len() | ||
} | ||
} | ||
|
||
macro_rules! get_value_slice( | ||
($encoded:expr, $entry_index:expr, $number_of_entries:expr, $indirection_index:expr, $ptr_type:ty, $from_raw_parts:ident, $as_ref:ident, $as_ptr:ident $(,)?) => {{ | ||
let value_start_offset = get_value_start_offset($encoded, $entry_index, $number_of_entries); | ||
let value_end_offset = get_value_end_offset($encoded, $entry_index, $number_of_entries); | ||
if let Some(indirection_index) = $indirection_index { | ||
let mut offset = value_start_offset + indirection_index * INDIRECT_FATPTR_LENGTH; | ||
let address = LittleEndian::read_u64(&$encoded[offset..]) as usize as $ptr_type; | ||
offset += size_of::<u64>(); | ||
let length = LittleEndian::read_u32(&$encoded[offset..]) as usize; | ||
unsafe { std::slice::$from_raw_parts(address, length) } | ||
} else { | ||
unsafe { std::slice::$from_raw_parts($encoded.$as_ptr().add(value_start_offset), value_end_offset - value_start_offset) } | ||
} | ||
}} | ||
); | ||
|
||
pub fn get_value( | ||
encoded: &[u8], | ||
entry_index: usize, | ||
number_of_entries: usize, | ||
indirection_index: Option<usize>, | ||
) -> &'static [u8] { | ||
get_value_slice!( | ||
encoded, | ||
entry_index, | ||
number_of_entries, | ||
indirection_index, | ||
*const u8, | ||
from_raw_parts, | ||
as_ref, | ||
as_ptr, | ||
) | ||
} | ||
|
||
pub fn get_value_mut( | ||
encoded: &mut [u8], | ||
entry_index: usize, | ||
number_of_entries: usize, | ||
indirection_index: Option<usize>, | ||
) -> &'static mut [u8] { | ||
get_value_slice!( | ||
encoded, | ||
entry_index, | ||
number_of_entries, | ||
indirection_index, | ||
*mut u8, | ||
from_raw_parts_mut, | ||
as_mut, | ||
as_mut_ptr, | ||
) | ||
} | ||
|
||
macro_rules! get_value_by_path( | ||
($encoded:expr, [$(($attribute:expr, $indirection_index:expr $(,)?)),* $(,)?] $(,)?) => {{ | ||
let encoded: &[u8] = $encoded; | ||
$( | ||
let (entry_index, number_of_entries) = resolve_attribute(encoded, $attribute); | ||
let indirection_index: Option<usize> = $indirection_index; | ||
let encoded = get_value( | ||
encoded, | ||
entry_index, | ||
number_of_entries, | ||
indirection_index, | ||
); | ||
)* | ||
encoded | ||
}} | ||
); | ||
|
||
macro_rules! get_value_by_path_mut( | ||
($encoded:expr, [$(($attribute:expr, $indirection_index:expr $(,)?)),* $(,)?] $(,)?) => {{ | ||
let encoded: &mut [u8] = $encoded; | ||
$( | ||
let (entry_index, number_of_entries) = resolve_attribute(encoded, $attribute); | ||
let indirection_index: Option<usize> = $indirection_index; | ||
let encoded = get_value_mut( | ||
encoded, | ||
entry_index, | ||
number_of_entries, | ||
indirection_index, | ||
); | ||
)* | ||
encoded | ||
}} | ||
); | ||
|
||
fn construct_eytzinger_order( | ||
encoded: &mut [u8], | ||
attributes: &[Attribute], | ||
mut in_index: usize, | ||
out_index: usize, | ||
) -> usize { | ||
if out_index >= attributes.len() { | ||
return in_index; | ||
} | ||
in_index = construct_eytzinger_order(encoded, attributes, in_index, 2 * out_index + 1); | ||
LittleEndian::write_u16( | ||
&mut encoded[out_index * size_of::<u16>()..], | ||
attributes[in_index] as u16, | ||
); | ||
construct_eytzinger_order(encoded, attributes, in_index + 1, 2 * out_index + 2) | ||
} | ||
|
||
pub fn encode_header(encoded: &mut [u8], attributes: &mut [Attribute]) -> usize { | ||
attributes.sort_unstable(); | ||
LittleEndian::write_u16(encoded, attributes.len() as u16); | ||
let attributes_offset = size_of::<u16>(); | ||
construct_eytzinger_order(&mut encoded[attributes_offset..], attributes, 0, 0); | ||
let value_offsets_offset = attributes_offset + attributes.len() * size_of::<u16>(); | ||
let value_start_offset = value_offsets_offset + attributes.len() * size_of::<u32>(); | ||
for (entry_index, attribute) in attributes.iter_mut().enumerate() { | ||
*attribute = get_attribute(encoded, entry_index); | ||
} | ||
value_start_offset | ||
} | ||
|
||
pub fn update_value_start_offset( | ||
encoded: &mut [u8], | ||
entry_index: usize, | ||
number_of_entries: usize, | ||
value_start_offset: usize, | ||
) { | ||
let value_offsets_offset = size_of::<u16>() + number_of_entries * size_of::<u16>(); | ||
LittleEndian::write_u32( | ||
&mut encoded[value_offsets_offset + entry_index * size_of::<u32>()..], | ||
value_start_offset as u32, | ||
); | ||
} | ||
|
||
pub fn update_value_fatptr(encoded: &mut [u8], mut value_start_offset: usize, slice: &[u8]) { | ||
LittleEndian::write_u64(&mut encoded[value_start_offset..], slice.as_ptr() as u64); | ||
value_start_offset += size_of::<u64>(); | ||
LittleEndian::write_u32(&mut encoded[value_start_offset..], slice.len() as u32); | ||
} | ||
|
||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] | ||
pub enum Attribute { | ||
NumberOfAccountsInTransaction = 0, | ||
AccountKey = 1, | ||
AccountIsExecutable = 2, | ||
AccountOwner = 3, | ||
AccountLamports = 4, | ||
AccountData = 5, | ||
ReturnData = 6, | ||
InvocationDepth = 7, | ||
InvocationDepthMax = 8, | ||
InvocationStack = 9, | ||
InstructionData = 10, | ||
NumberOfAccountsInInstruction = 11, | ||
NumberOfProgramsInInstruction = 12, | ||
InstructionAccountIndices = 13, | ||
AccountIsSigner = 14, | ||
AccountIsWritable = 15, | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_abi_encoding() { | ||
let indirect_value = vec![2; 32]; | ||
let entries = [ | ||
(Attribute::NumberOfAccountsInTransaction, vec![], 4), | ||
( | ||
Attribute::AccountData, | ||
vec![indirect_value.as_slice()], | ||
INDIRECT_FATPTR_LENGTH, | ||
), | ||
(Attribute::AccountKey, vec![], 32), | ||
]; | ||
|
||
// Encode | ||
let mut attributes = entries | ||
.iter() | ||
.map(|(attribute, _, _)| *attribute) | ||
.collect::<Vec<_>>(); | ||
let mut encoded = vec![0; predict_header_size(attributes.len())]; | ||
let mut value_start_offset = encode_header(&mut encoded, attributes.as_mut_slice()); | ||
for (reodered_entry_index, attribute) in attributes.iter().enumerate() { | ||
let entry_index = entries | ||
.iter() | ||
.position(|(attribute_to_cmp, _, _)| attribute == attribute_to_cmp) | ||
.unwrap(); | ||
update_value_start_offset( | ||
&mut encoded, | ||
reodered_entry_index, | ||
entries.len(), | ||
value_start_offset, | ||
); | ||
encoded.append(&mut vec![*attribute as u8; entries[entry_index].2]); | ||
for slice in entries[entry_index].1.iter() { | ||
update_value_fatptr(&mut encoded, value_start_offset, slice); | ||
} | ||
value_start_offset = encoded.len(); | ||
} | ||
|
||
// Decode | ||
let number_of_entries = get_number_of_entries(&encoded); | ||
for (attribute, indirect_values, length) in entries.iter() { | ||
let reodered_entry_index = resolve_attribute(&encoded, *attribute).0; | ||
assert_eq!(*attribute, get_attribute(&encoded, reodered_entry_index)); | ||
if indirect_values.is_empty() { | ||
let value_slice = | ||
get_value(&encoded, reodered_entry_index, number_of_entries, None); | ||
assert_eq!(*length, value_slice.len()); | ||
} else { | ||
for (indirection_index, indirect_value) in indirect_values.iter().enumerate() { | ||
let value_slice = get_value( | ||
&encoded, | ||
reodered_entry_index, | ||
number_of_entries, | ||
Some(indirection_index), | ||
); | ||
assert_eq!(indirect_value, &value_slice); | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters