use crate::executor::host;
use alloc::vec::Vec;
use core::{fmt, ops, str};
pub use super::TrieEntryVersion;
pub fn find_embedded_runtime_version(
binary_wasm_module: &[u8],
) -> Result<Option<CoreVersion>, FindEmbeddedRuntimeVersionError> {
let (runtime_version_content, runtime_apis_content) =
match find_encoded_embedded_runtime_version_apis(binary_wasm_module) {
Ok(EmbeddedRuntimeVersionApis {
runtime_version_content: Some(v),
runtime_apis_content: Some(a),
}) => (v, a),
Ok(EmbeddedRuntimeVersionApis {
runtime_version_content: None,
runtime_apis_content: None,
}) => return Ok(None),
Ok(_) => return Err(FindEmbeddedRuntimeVersionError::CustomSectionsPresenceMismatch),
Err(err) => return Err(FindEmbeddedRuntimeVersionError::FindSections(err)),
};
let mut decoded_runtime_version = match decode(runtime_version_content) {
Ok(d) => d,
Err(()) => return Err(FindEmbeddedRuntimeVersionError::RuntimeVersionDecode),
};
decoded_runtime_version.apis =
match CoreVersionApisRefIter::from_slice_no_length(runtime_apis_content) {
Ok(d) => d,
Err(err) => return Err(FindEmbeddedRuntimeVersionError::RuntimeApisDecode(err)),
};
Ok(Some(CoreVersion(
decoded_runtime_version.scale_encoding_vec(),
)))
}
#[derive(Debug, derive_more::Display, derive_more::Error, Clone)]
pub enum FindEmbeddedRuntimeVersionError {
#[display("{_0}")]
FindSections(FindEncodedEmbeddedRuntimeVersionApisError),
CustomSectionsPresenceMismatch,
RuntimeVersionDecode,
#[display("{_0}")]
RuntimeApisDecode(CoreVersionApisFromSliceErr),
}
#[derive(Debug, Copy, Clone)]
pub struct EmbeddedRuntimeVersionApis<'a> {
pub runtime_version_content: Option<&'a [u8]>,
pub runtime_apis_content: Option<&'a [u8]>,
}
pub fn find_encoded_embedded_runtime_version_apis(
binary_wasm_module: &'_ [u8],
) -> Result<EmbeddedRuntimeVersionApis<'_>, FindEncodedEmbeddedRuntimeVersionApisError> {
let mut parser =
nom::combinator::all_consuming(nom::combinator::complete(nom::sequence::preceded(
(
nom::bytes::streaming::tag(&b"\0asm"[..]),
nom::bytes::streaming::tag(&[0x1, 0x0, 0x0, 0x0][..]),
),
nom::multi::fold_many0(
nom::combinator::complete(wasm_section),
|| (None, None),
move |prev_found, in_section| {
match (prev_found, in_section) {
(prev_found, None) => prev_found,
(
prev_found @ (Some(_), _),
Some(WasmSection {
name: b"runtime_version",
..
}),
) => prev_found,
(
prev_found @ (_, Some(_)),
Some(WasmSection {
name: b"runtime_apis",
..
}),
) => prev_found,
(
(None, prev_rt_apis),
Some(WasmSection {
name: b"runtime_version",
content,
}),
) => (Some(content), prev_rt_apis),
(
(prev_rt_version, None),
Some(WasmSection {
name: b"runtime_apis",
content,
}),
) => (prev_rt_version, Some(content)),
(prev_found, Some(_)) => prev_found,
}
},
),
)));
let (runtime_version_content, runtime_apis_content) =
match nom::Parser::parse(&mut parser, binary_wasm_module) {
Ok((_, content)) => content,
Err(_) => return Err(FindEncodedEmbeddedRuntimeVersionApisError::FailedToParse),
};
Ok(EmbeddedRuntimeVersionApis {
runtime_version_content,
runtime_apis_content,
})
}
#[derive(Debug, derive_more::Display, derive_more::Error, Clone)]
pub enum FindEncodedEmbeddedRuntimeVersionApisError {
FailedToParse,
}
#[derive(Debug, derive_more::Display, derive_more::Error, Clone)]
pub enum CoreVersionError {
Decode,
#[display("Error while starting the execution of the `Core_version` function: {_0}")]
Start(host::StartErr),
#[display("Error during the execution of the `Core_version` function: {_0}")]
Run(host::Error),
ForbiddenHostFunction,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CoreVersion(Vec<u8>);
impl CoreVersion {
pub fn from_slice(input: Vec<u8>) -> Result<Self, Vec<u8>> {
if decode(&input).is_err() {
return Err(input);
}
Ok(CoreVersion(input))
}
pub fn decode(&'_ self) -> CoreVersionRef<'_> {
decode(&self.0).unwrap()
}
}
impl AsRef<[u8]> for CoreVersion {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CoreVersionRef<'a> {
pub spec_name: &'a str,
pub impl_name: &'a str,
pub authoring_version: u32,
pub spec_version: u32,
pub impl_version: u32,
pub apis: CoreVersionApisRefIter<'a>,
pub transaction_version: Option<u32>,
pub state_version: Option<TrieEntryVersion>,
}
impl CoreVersionRef<'_> {
pub fn scale_encoding_vec(&self) -> Vec<u8> {
let num_apis = self.apis.clone().count();
let mut out = Vec::<u8>::with_capacity(
2 + self.spec_name.len() + 2 + self.impl_name.len() + 4 + 4 + 4 + num_apis * 12 + 4 + 1,
);
out.extend(crate::util::encode_scale_compact_usize(self.spec_name.len()).as_ref());
out.extend(self.spec_name.as_bytes());
out.extend(crate::util::encode_scale_compact_usize(self.impl_name.len()).as_ref());
out.extend(self.impl_name.as_bytes());
out.extend(self.authoring_version.to_le_bytes());
out.extend(self.spec_version.to_le_bytes());
out.extend(self.impl_version.to_le_bytes());
out.extend(crate::util::encode_scale_compact_usize(num_apis).as_ref());
for api in self.apis.clone() {
out.extend(api.name_hash);
out.extend(api.version.to_le_bytes());
}
if let Some(transaction_version) = self.transaction_version {
out.extend(transaction_version.to_le_bytes());
}
if let Some(state_version) = self.state_version {
out.extend(u8::from(state_version).to_le_bytes());
}
out
}
}
#[derive(Clone)]
pub struct CoreVersionApisRefIter<'a> {
inner: &'a [u8],
}
impl<'a> CoreVersionApisRefIter<'a> {
pub fn from_slice_no_length(input: &'a [u8]) -> Result<Self, CoreVersionApisFromSliceErr> {
let result: Result<_, nom::Err<nom::error::Error<&[u8]>>> = nom::Parser::parse(
&mut nom::combinator::all_consuming(nom::combinator::complete(nom::combinator::map(
nom::combinator::recognize(nom::multi::fold_many0(
nom::combinator::complete(core_version_api),
|| {},
|(), _| (),
)),
|inner| CoreVersionApisRefIter { inner },
))),
input,
);
match result {
Ok((_, me)) => Ok(me),
Err(_) => Err(CoreVersionApisFromSliceErr()),
}
}
pub fn find_version(&self, api: &str) -> Option<u32> {
self.find_versions([api])[0]
}
pub fn find_versions<const N: usize>(&self, apis: [&str; N]) -> [Option<u32>; N] {
let hashed = core::array::from_fn::<_, N, _>(|n| hash_api_name(apis[n]));
let mut out = [None; N];
for api in self.clone() {
for (n, expected) in hashed.iter().enumerate() {
if *expected == api.name_hash {
match out[n] {
Some(ref mut v) if *v < api.version => *v = api.version,
Some(_) => {}
ref mut v @ None => *v = Some(api.version),
}
}
}
}
out
}
pub fn contains(&self, api_name: &str, version_number: impl ops::RangeBounds<u32>) -> bool {
self.contains_hashed(&hash_api_name(api_name), version_number)
}
pub fn contains_hashed(
&self,
api_name_hash: &[u8; 8],
version_number: impl ops::RangeBounds<u32>,
) -> bool {
self.clone()
.any(|api| api.name_hash == *api_name_hash && version_number.contains(&api.version))
}
}
impl Iterator for CoreVersionApisRefIter<'_> {
type Item = CoreVersionApi;
fn next(&mut self) -> Option<Self::Item> {
if self.inner.is_empty() {
return None;
}
match core_version_api::<nom::error::Error<&[u8]>>(self.inner) {
Ok((rest, item)) => {
self.inner = rest;
Some(item)
}
Err(_) => unreachable!(),
}
}
}
impl PartialEq for CoreVersionApisRefIter<'_> {
fn eq(&self, other: &Self) -> bool {
let mut a = self.clone();
let mut b = other.clone();
loop {
match (a.next(), b.next()) {
(Some(a), Some(b)) if a == b => {}
(None, None) => return true,
_ => return false,
}
}
}
}
impl Eq for CoreVersionApisRefIter<'_> {}
impl fmt::Debug for CoreVersionApisRefIter<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_list().entries(self.clone()).finish()
}
}
#[derive(Debug, Clone, derive_more::Display, derive_more::Error)]
#[display("Error decoding core version APIs")]
pub struct CoreVersionApisFromSliceErr();
pub fn hash_api_name(api_name: &str) -> [u8; 8] {
let result = blake2_rfc::blake2b::blake2b(8, &[], api_name.as_bytes());
result.as_bytes().try_into().unwrap()
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CoreVersionApi {
pub name_hash: [u8; 8],
pub version: u32,
}
fn decode(scale_encoded: &'_ [u8]) -> Result<CoreVersionRef<'_>, ()> {
let result: nom::IResult<_, _> = nom::Parser::parse(
&mut nom::combinator::all_consuming(nom::combinator::complete(nom::combinator::map(
(
crate::util::nom_string_decode,
crate::util::nom_string_decode,
nom::number::streaming::le_u32,
nom::number::streaming::le_u32,
nom::number::streaming::le_u32,
core_version_apis,
nom::branch::alt((
nom::combinator::complete(nom::combinator::map(
nom::number::streaming::le_u32,
Some,
)),
nom::combinator::map(nom::combinator::eof, |_| None),
)),
nom::branch::alt((
nom::combinator::complete(nom::combinator::map(
nom::bytes::streaming::tag(&[0][..]),
|_| Some(TrieEntryVersion::V0),
)),
nom::combinator::complete(nom::combinator::map(
nom::bytes::streaming::tag(&[1][..]),
|_| Some(TrieEntryVersion::V1),
)),
nom::combinator::map(nom::combinator::eof, |_| None),
)),
),
|(
spec_name,
impl_name,
authoring_version,
spec_version,
impl_version,
apis,
transaction_version,
state_version,
)| CoreVersionRef {
spec_name,
impl_name,
authoring_version,
spec_version,
impl_version,
apis,
transaction_version,
state_version,
},
))),
scale_encoded,
);
match result {
Ok((_, out)) => Ok(out),
Err(nom::Err::Error(_) | nom::Err::Failure(_)) => Err(()),
Err(_) => unreachable!(),
}
}
fn core_version_apis<'a, E: nom::error::ParseError<&'a [u8]>>(
bytes: &'a [u8],
) -> nom::IResult<&'a [u8], CoreVersionApisRefIter<'a>, E> {
nom::Parser::parse(
&mut nom::combinator::map(
nom::combinator::flat_map(crate::util::nom_scale_compact_usize, |num_elems| {
nom::combinator::recognize(nom::multi::fold_many_m_n(
num_elems,
num_elems,
core_version_api,
|| {},
|(), _| (),
))
}),
|inner| CoreVersionApisRefIter { inner },
),
bytes,
)
}
fn core_version_api<'a, E: nom::error::ParseError<&'a [u8]>>(
bytes: &'a [u8],
) -> nom::IResult<&'a [u8], CoreVersionApi, E> {
nom::Parser::parse(
&mut nom::combinator::map(
(
nom::bytes::streaming::take(8u32),
nom::number::streaming::le_u32,
),
move |(name, version)| CoreVersionApi {
name_hash: <[u8; 8]>::try_from(name).unwrap(),
version,
},
),
bytes,
)
}
struct WasmSection<'a> {
name: &'a [u8],
content: &'a [u8],
}
fn wasm_section(bytes: &'_ [u8]) -> nom::IResult<&'_ [u8], Option<WasmSection<'_>>> {
nom::Parser::parse(
&mut nom::branch::alt((
nom::combinator::map(
nom::combinator::map_parser(
nom::sequence::preceded(
nom::bytes::streaming::tag(&[0][..]),
nom::multi::length_data(nom::combinator::map_opt(
crate::util::leb128::nom_leb128_u64,
|n| u32::try_from(n).ok(),
)),
),
(
nom::multi::length_data(nom::combinator::map_opt(
crate::util::leb128::nom_leb128_u64,
|n| u32::try_from(n).ok(),
)),
nom::combinator::rest,
),
),
|(name, content)| Some(WasmSection { name, content }),
),
nom::combinator::map(
(
nom::number::streaming::u8,
nom::multi::length_data(nom::combinator::map_opt(
crate::util::leb128::nom_leb128_u64,
|n| u32::try_from(n).ok(),
)),
),
|_| None,
),
)),
bytes,
)
}