Skip to content

Commit

Permalink
Use CounterUnpacker, fix rand_metadata_feature(), fix entry count typ…
Browse files Browse the repository at this point in the history
…e, fix rand_mana_allotment()
  • Loading branch information
Thoralf-M committed Jan 16, 2024
1 parent 9d80a70 commit fc5931d
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 39 deletions.
8 changes: 4 additions & 4 deletions sdk/src/types/block/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::types::block::{
output::{
feature::{BlockIssuerKeyCount, FeatureCount},
unlock_condition::UnlockConditionCount,
AccountId, AnchorId, ChainId, MetadataFeatureKeyLength, MetadataFeatureLength, MetadataFeatureValueLength,
AccountId, AnchorId, ChainId, MetadataFeatureEntryCount, MetadataFeatureKeyLength, MetadataFeatureValueLength,
NativeTokenCount, NftId, OutputIndex, TagFeatureLength,
},
payload::{
Expand Down Expand Up @@ -110,7 +110,7 @@ pub enum Error {
InvalidBlockLength(usize),
InvalidManaValue(u64),
InvalidMetadataFeature(String),
InvalidMetadataFeatureLength(<MetadataFeatureLength as TryFrom<usize>>::Error),
InvalidMetadataFeatureEntryCount(<MetadataFeatureEntryCount as TryFrom<usize>>::Error),
InvalidMetadataFeatureKeyLength(<MetadataFeatureKeyLength as TryFrom<usize>>::Error),
InvalidMetadataFeatureValueLength(<MetadataFeatureValueLength as TryFrom<usize>>::Error),
InvalidNativeTokenCount(<NativeTokenCount as TryFrom<usize>>::Error),
Expand Down Expand Up @@ -316,8 +316,8 @@ impl fmt::Display for Error {
Self::InvalidMetadataFeature(e) => {
write!(f, "invalid metadata feature: {e}")
}
Self::InvalidMetadataFeatureLength(length) => {
write!(f, "invalid metadata feature length: {length}")
Self::InvalidMetadataFeatureEntryCount(count) => {
write!(f, "invalid metadata feature entry count: {count}")
}
Self::InvalidMetadataFeatureKeyLength(length) => {
write!(f, "invalid metadata feature key length: {length}")
Expand Down
65 changes: 52 additions & 13 deletions sdk/src/types/block/output/feature/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,78 @@ use core::ops::{Deref, RangeInclusive};

use packable::{
bounded::{BoundedU16, BoundedU8},
error::{UnpackError, UnpackErrorExt},
packer::Packer,
prefix::{BTreeMapPrefix, BoxedSlicePrefix},
PackableExt,
unpacker::{CounterUnpacker, Unpacker},
Packable, PackableExt,
};

use crate::types::block::{output::StorageScore, protocol::WorkScore, Error};

pub(crate) type MetadataFeatureLength =
BoundedU16<{ *MetadataFeature::LENGTH_RANGE.start() }, { *MetadataFeature::LENGTH_RANGE.end() }>;

pub(crate) type MetadataFeatureEntryCount = BoundedU8<1, { u8::MAX }>;
pub(crate) type MetadataFeatureKeyLength = BoundedU8<1, { u8::MAX }>;
pub(crate) type MetadataFeatureValueLength = BoundedU16<0, { u16::MAX }>;

type MetadataBTreeMapPrefix = BTreeMapPrefix<
BoxedSlicePrefix<u8, MetadataFeatureKeyLength>,
BoxedSlicePrefix<u8, MetadataFeatureValueLength>,
MetadataFeatureLength,
MetadataFeatureEntryCount,
>;

type MetadataBTreeMap =
BTreeMap<BoxedSlicePrefix<u8, MetadataFeatureKeyLength>, BoxedSlicePrefix<u8, MetadataFeatureValueLength>>;

/// Defines metadata, arbitrary binary data, that will be stored in the output.
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, packable::Packable)]
#[packable(unpack_error = Error, with = |err| Error::InvalidMetadataFeature(err.to_string()))]
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct MetadataFeature(
// Binary data.
#[packable(verify_with = verify_packable)] pub(crate) MetadataBTreeMapPrefix,
pub(crate) MetadataBTreeMapPrefix,
);

fn verify_packable<const VERIFY: bool>(map: &MetadataBTreeMapPrefix) -> Result<(), Error> {
verify_keys_packable::<VERIFY>(map)?;
verify_length_packable::<VERIFY>(map)?;
Ok(())
impl Packable for MetadataFeature {
type UnpackError = Error;
type UnpackVisitor = ();

fn pack<P: Packer>(&self, packer: &mut P) -> Result<(), P::Error> {
self.0.pack(packer)?;

Ok(())
}

fn unpack<U: Unpacker, const VERIFY: bool>(
unpacker: &mut U,
visitor: &Self::UnpackVisitor,
) -> Result<Self, UnpackError<Self::UnpackError, U::Error>> {
let mut unpacker = CounterUnpacker::new(unpacker);
let start_opt = unpacker.read_bytes();

let map = MetadataBTreeMapPrefix::unpack::<_, VERIFY>(&mut unpacker, visitor)
.map_packable_err(|e| Error::InvalidMetadataFeature(e.to_string()))?;

verify_keys_packable::<true>(&map).map_err(UnpackError::Packable)?;

let packed_len = if let (Some(start), Some(end)) = (start_opt, unpacker.read_bytes()) {
end - start
} else {
map.packed_len()
};

let packed_len = u16::try_from(packed_len).map_err(|_| {
UnpackError::Packable(Error::InvalidMetadataFeature(format!(
"Out of bounds byte length: {}",
packed_len + 1
)))
})? + 1; // +1 for the type byte

if !Self::LENGTH_RANGE.contains(&(packed_len)) {
return Err(UnpackError::Packable(Error::InvalidMetadataFeature(format!(
"Out of bounds byte length: {packed_len}"
))));
}

Ok(Self(map))
}
}

fn verify_keys_packable<const VERIFY: bool>(map: &MetadataBTreeMapPrefix) -> Result<(), Error> {
Expand Down Expand Up @@ -140,7 +179,7 @@ fn metadata_feature_from_iter(data: impl IntoIterator<Item = (Vec<u8>, Vec<u8>)>
}

Ok(MetadataFeature(
res.try_into().map_err(Error::InvalidMetadataFeatureLength)?,
res.try_into().map_err(Error::InvalidMetadataFeatureEntryCount)?,
))
}

Expand Down
2 changes: 1 addition & 1 deletion sdk/src/types/block/output/feature/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub use self::metadata::irc_27::{Attribute, Irc27Metadata};
pub use self::metadata::irc_30::Irc30Metadata;
pub(crate) use self::{
block_issuer::BlockIssuerKeyCount,
metadata::{MetadataFeatureKeyLength, MetadataFeatureLength, MetadataFeatureValueLength},
metadata::{MetadataFeatureEntryCount, MetadataFeatureKeyLength, MetadataFeatureValueLength},
tag::TagFeatureLength,
};
pub use self::{
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/types/block/output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub use self::{
unlock_condition::{UnlockCondition, UnlockConditions},
};
pub(crate) use self::{
feature::{MetadataFeatureKeyLength, MetadataFeatureLength, MetadataFeatureValueLength, TagFeatureLength},
feature::{MetadataFeatureEntryCount, MetadataFeatureKeyLength, MetadataFeatureValueLength, TagFeatureLength},
native_token::NativeTokenCount,
output_id::OutputIndex,
unlock_condition::AddressUnlockCondition,
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/types/block/rand/mana.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::types::block::{
pub fn rand_mana_allotment(params: &ProtocolParameters) -> ManaAllotment {
ManaAllotment::new(
rand_account_id(),
rand_number_range(0..params.mana_parameters().max_mana()),
rand_number_range(1..params.mana_parameters().max_mana()),
)
.unwrap()
}
51 changes: 35 additions & 16 deletions sdk/src/types/block/rand/output/feature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,32 +36,51 @@ pub fn rand_issuer_feature() -> IssuerFeature {
/// Generates a random [`MetadataFeature`].
pub fn rand_metadata_feature() -> MetadataFeature {
let mut map = BTreeMap::new();
let mut total_size = 0;
// Starting at 2 for type + entries count bytes
let mut total_size = 2;
let max_size = *MetadataFeature::LENGTH_RANGE.end() as usize;
let key_prefix_length = 1;
let value_prefix_length = 2;

for _ in 0..10 {
if total_size >= (*MetadataFeature::LENGTH_RANGE.end() - 1) as usize - u8::MAX as usize {
// +1 since min key size is 1
if total_size > (max_size - (key_prefix_length + value_prefix_length + 1)) as usize {
break;
}

// Key length
total_size += 1;
let key = Alphanumeric.sample_string(
&mut rand::thread_rng(),
rand_number_range(Range {
start: 1,
end: u8::MAX.into(),
}),
);
total_size += key_prefix_length;
let max_val = if max_size - total_size - value_prefix_length < u8::MAX as usize {
max_size - total_size - value_prefix_length
} else {
u8::MAX.into()
};

let key = if max_val == 1 {
"a".to_string()
} else {
Alphanumeric.sample_string(
&mut rand::thread_rng(),
rand_number_range(Range { start: 1, end: max_val }),
)
};
total_size += key.as_bytes().len();

if total_size >= *MetadataFeature::LENGTH_RANGE.end() as usize - 2 {
if total_size > max_size - value_prefix_length {
// println!("breaking before adding more");
break;
}

// Value length
total_size += 2;
let bytes = rand_bytes(rand_number_range(Range {
start: 0,
end: *MetadataFeature::LENGTH_RANGE.end() as usize - total_size,
}) as usize);
total_size += value_prefix_length;
let bytes = if max_size - total_size == 0 {
vec![]
} else {
rand_bytes(rand_number_range(Range {
start: 0,
end: max_size - total_size,
}))
};
total_size += bytes.len();

map.insert(key.into(), bytes);
Expand Down
6 changes: 3 additions & 3 deletions sdk/tests/types/output/feature/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ fn serde_roundtrip() {
#[test]
fn unpack_invalid_order() {
assert!(matches!(
MetadataFeature::unpack_verified([3, 0, 1, 99, 0, 0, 1, 98, 0, 0, 1, 97, 0, 0], &()),
MetadataFeature::unpack_verified([3, 1, 99, 0, 0, 1, 98, 0, 0, 1, 97, 0, 0], &()),
Err(UnpackError::Packable(Error::InvalidMetadataFeature(error_msg))) if &error_msg == "unordered map"
));
}

#[test]
fn unpack_invalid_length() {
assert!(matches!(
MetadataFeature::unpack_verified([vec![1, 0, 1, 33, 0, 32], vec![0u8; 8192]].concat(), &()),
Err(UnpackError::Packable(Error::InvalidMetadataFeature(len))) if &len == "Out of bounds byte length: 8199"
MetadataFeature::unpack_verified([vec![1, 1, 33, 0, 32], vec![0u8; 8192]].concat(), &()),
Err(UnpackError::Packable(Error::InvalidMetadataFeature(len))) if &len == "Out of bounds byte length: 8198"
));
}

0 comments on commit fc5931d

Please sign in to comment.