marine_module_info_parser/manifest/
module_manifest.rs

1/*
2 * Copyright 2020 Fluence Labs Limited
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/// Describes manifest of a Wasm module in the Fluence network.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct ModuleManifest {
20    pub authors: String,
21    pub version: semver::Version,
22    pub description: String,
23    pub repository: String,
24    pub build_time: chrono::DateTime<chrono::FixedOffset>,
25}
26
27use super::ManifestError;
28
29use std::convert::TryFrom;
30use std::str::FromStr;
31
32type Result<T> = std::result::Result<T, ManifestError>;
33
34impl TryFrom<&[u8]> for ModuleManifest {
35    type Error = ManifestError;
36
37    #[rustfmt::skip]
38    fn try_from(value: &[u8]) -> Result<Self> {
39        let (authors, next_offset) = try_extract_field_as_string(value, 0, "authors")?;
40        let (version, next_offset) = try_extract_field_as_version(value, next_offset, "version")?;
41        let (description, next_offset) = try_extract_field_as_string(value, next_offset, "description")?;
42        let (repository, next_offset) = try_extract_field_as_string(value, next_offset, "repository")?;
43        let (build_time, next_offset) = try_extract_field_as_string(value, next_offset, "build time")?;
44
45        if next_offset != value.len() {
46            return Err(ManifestError::ManifestRemainderNotEmpty)
47        }
48
49        let build_time = chrono::DateTime::parse_from_rfc3339(&build_time)?;
50
51        let manifest = ModuleManifest {
52            authors,
53            version,
54            description,
55            repository,
56            build_time
57        };
58
59        Ok(manifest)
60    }
61}
62
63fn try_extract_field_as_string(
64    raw_manifest: &[u8],
65    offset: usize,
66    field_name: &'static str,
67) -> Result<(String, usize)> {
68    let raw_manifest = &raw_manifest[offset..];
69    let (field_as_bytes, read_len) = try_extract_prefixed_field(raw_manifest, field_name)?;
70    let field_as_string = try_to_str(field_as_bytes, field_name)?.to_string();
71
72    Ok((field_as_string, offset + read_len))
73}
74
75fn try_extract_field_as_version(
76    raw_manifest: &[u8],
77    offset: usize,
78    field_name: &'static str,
79) -> Result<(semver::Version, usize)> {
80    let raw_manifest = &raw_manifest[offset..];
81    let (field_as_bytes, read_len) = try_extract_prefixed_field(raw_manifest, field_name)?;
82    let field_as_str = try_to_str(field_as_bytes, field_name)?;
83    let version = semver::Version::from_str(field_as_str)?;
84
85    Ok((version, offset + read_len))
86}
87
88const PREFIX_SIZE: usize = std::mem::size_of::<u64>();
89
90fn try_extract_prefixed_field<'a>(
91    array: &'a [u8],
92    field_name: &'static str,
93) -> Result<(&'a [u8], usize)> {
94    let field_len = try_extract_field_len(array, field_name)?;
95    let field = try_extract_field(array, field_len, field_name)?;
96
97    let read_size = PREFIX_SIZE + field.len();
98    Ok((field, read_size))
99}
100
101fn try_extract_field_len(array: &[u8], field_name: &'static str) -> Result<usize> {
102    if array.len() < PREFIX_SIZE {
103        return Err(ManifestError::NotEnoughBytesForPrefix(field_name));
104    }
105
106    let mut field_len = [0u8; PREFIX_SIZE];
107    field_len.copy_from_slice(&array[0..PREFIX_SIZE]);
108
109    let field_len = u64::from_le_bytes(field_len);
110    // TODO: Until we use Wasm32 and compiles our node to x86_64, converting to usize is sound
111    if field_len.checked_add(PREFIX_SIZE as u64).is_none()
112        || usize::try_from(field_len + PREFIX_SIZE as u64).is_err()
113    {
114        return Err(ManifestError::TooBigFieldSize(field_name, field_len));
115    }
116
117    // it's safe to convert it to usize because it's been checked
118    Ok(field_len as usize)
119}
120
121fn try_extract_field<'a>(
122    array: &'a [u8],
123    field_len: usize,
124    field_name: &'static str,
125) -> Result<&'a [u8]> {
126    if array.len() < PREFIX_SIZE + field_len {
127        return Err(ManifestError::NotEnoughBytesForField(field_name, field_len));
128    }
129
130    let field = &array[PREFIX_SIZE..PREFIX_SIZE + field_len];
131    Ok(field)
132}
133
134fn try_to_str<'v>(value: &'v [u8], field_name: &'static str) -> Result<&'v str> {
135    match std::str::from_utf8(value) {
136        Ok(s) => Ok(s),
137        Err(e) => Err(ManifestError::FieldNotValidUtf8(field_name, e)),
138    }
139}
140
141use std::fmt;
142
143impl fmt::Display for ModuleManifest {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        writeln!(f, "authors:     {}", self.authors)?;
146        writeln!(f, "version:     {}", self.version)?;
147        writeln!(f, "description: {}", self.description)?;
148        writeln!(f, "repository:  {}", self.repository)?;
149        write!(f, "build time:  {} UTC", self.build_time)
150    }
151}