Skip to main content

wasmer_package/
utils.rs

1#![allow(
2    clippy::result_large_err,
3    reason = "WasmerPackageError is large, but not often used"
4)]
5
6#[cfg(feature = "authoring")]
7use bytes::Buf;
8use bytes::Bytes;
9#[cfg(feature = "authoring")]
10use std::io::{BufRead, BufReader};
11use std::{
12    fs::File,
13    io::{Read, Seek},
14    path::Path,
15};
16use wasmer_types::Features;
17use webc::{Container, ContainerError, Version};
18
19use crate::WasmerPackageError;
20#[cfg(feature = "authoring")]
21use crate::package::Package;
22
23/// Check if something looks like a `*.tar.gz` file.
24fn is_tarball(mut file: impl Read + Seek) -> bool {
25    /// Magic bytes for a `*.tar.gz` file according to
26    /// [Wikipedia](https://en.wikipedia.org/wiki/List_of_file_signatures).
27    const TAR_GZ_MAGIC_BYTES: [u8; 2] = [0x1F, 0x8B];
28
29    let mut buffer = [0_u8; 2];
30    let result = match file.read_exact(&mut buffer) {
31        Ok(_) => buffer == TAR_GZ_MAGIC_BYTES,
32        Err(_) => false,
33    };
34
35    let _ = file.rewind();
36
37    result
38}
39
40pub fn from_disk(path: impl AsRef<Path>) -> Result<Container, WasmerPackageError> {
41    let path = path.as_ref();
42
43    if path.is_dir() {
44        #[cfg(feature = "authoring")]
45        return parse_dir(path);
46        #[cfg(not(feature = "authoring"))]
47        return Err(ContainerError::FeatureNotEnabled {
48            feature: "authoring",
49        }
50        .into());
51    }
52
53    let mut f = File::open(path).map_err(|error| ContainerError::Open {
54        error,
55        path: path.to_path_buf(),
56    })?;
57
58    if is_tarball(&mut f) {
59        #[cfg(feature = "authoring")]
60        return parse_tarball(BufReader::new(f));
61        #[cfg(not(feature = "authoring"))]
62        return Err(ContainerError::FeatureNotEnabled {
63            feature: "authoring",
64        }
65        .into());
66    }
67
68    match webc::detect(&mut f) {
69        Ok(Version::V2) => parse_v2_mmap(f).map_err(Into::into),
70        Ok(Version::V3) => parse_v3_mmap(f).map_err(Into::into),
71        Ok(other) => {
72            // fall back to the allocating generic version
73            let mut buffer = Vec::new();
74            f.rewind()
75                .and_then(|_| f.read_to_end(&mut buffer))
76                .map_err(|error| ContainerError::Read {
77                    path: path.to_path_buf(),
78                    error,
79                })?;
80
81            Container::from_bytes_and_version(buffer.into(), other).map_err(Into::into)
82        }
83        Err(e) => Err(ContainerError::Detect(e).into()),
84    }
85}
86
87/// Check if the data looks like a webc.
88pub fn is_container(bytes: &[u8]) -> bool {
89    is_tarball(std::io::Cursor::new(bytes)) || webc::detect(bytes).is_ok()
90}
91
92pub fn from_bytes(bytes: impl Into<Bytes>) -> Result<Container, WasmerPackageError> {
93    let bytes: Bytes = bytes.into();
94
95    if is_tarball(std::io::Cursor::new(&bytes)) {
96        #[cfg(feature = "authoring")]
97        return parse_tarball(bytes.reader());
98        #[cfg(not(feature = "authoring"))]
99        return Err(ContainerError::FeatureNotEnabled {
100            feature: "authoring",
101        }
102        .into());
103    }
104
105    let version = webc::detect(bytes.as_ref())?;
106    Container::from_bytes_and_version(bytes, version).map_err(Into::into)
107}
108
109#[allow(clippy::result_large_err)]
110#[cfg(feature = "authoring")]
111fn parse_tarball(reader: impl BufRead) -> Result<Container, WasmerPackageError> {
112    let pkg = Package::from_tarball(reader)?;
113    Ok(Container::new(pkg))
114}
115
116#[allow(clippy::result_large_err)]
117#[cfg(feature = "authoring")]
118fn parse_dir(path: &Path) -> Result<Container, WasmerPackageError> {
119    let wasmer_toml = path.join("wasmer.toml");
120    let pkg = Package::from_manifest(wasmer_toml)?;
121    Ok(Container::new(pkg))
122}
123
124#[allow(clippy::result_large_err)]
125fn parse_v2_mmap(f: File) -> Result<Container, ContainerError> {
126    // Note: OwnedReader::from_file() will automatically try to
127    // use a memory-mapped file when possible.
128    let webc = webc::v2::read::OwnedReader::from_file(f)?;
129    Ok(Container::new(webc))
130}
131
132#[allow(clippy::result_large_err)]
133fn parse_v3_mmap(f: File) -> Result<Container, ContainerError> {
134    // Note: OwnedReader::from_file() will automatically try to
135    // use a memory-mapped file when possible.
136    let webc = webc::v3::read::OwnedReader::from_file(f)?;
137    Ok(Container::new(webc))
138}
139
140/// Convert a `Features` object to a list of WebAssembly feature strings
141/// that can be used in annotations.
142///
143/// This maps each enabled feature to its corresponding string identifier
144/// used in the WebAssembly ecosystem.
145pub fn features_to_wasm_annotations(features: &Features) -> Vec<String> {
146    let mut feature_strings = Vec::new();
147
148    if features.simd {
149        feature_strings.push("simd".to_string());
150    }
151    if features.bulk_memory {
152        feature_strings.push("bulk-memory".to_string());
153    }
154    if features.reference_types {
155        feature_strings.push("reference-types".to_string());
156    }
157    if features.multi_value {
158        feature_strings.push("multi-value".to_string());
159    }
160    if features.threads {
161        feature_strings.push("threads".to_string());
162    }
163    if features.exceptions {
164        feature_strings.push("exception-handling".to_string());
165    }
166    if features.memory64 {
167        feature_strings.push("memory64".to_string());
168    }
169    if features.wide_arithmetic {
170        feature_strings.push("wide-arithmetic".to_string());
171    }
172    if features.tail_call {
173        feature_strings.push("tail-call".to_string());
174    }
175    if features.multi_memory {
176        feature_strings.push("multi-memory".to_string());
177    }
178    // Note: We don't currently include module_linking or extended_const in the feature strings.
179
180    feature_strings
181}
182
183/// Create a `Features` object from a list of WebAssembly feature strings.
184///
185/// This is the inverse of `features_to_wasm_annotations`, mapping string identifiers
186/// back to Features settings.
187pub fn wasm_annotations_to_features(feature_strings: &[String]) -> Features {
188    let mut features = Features::default();
189
190    // Initialize with default values
191    features
192        .simd(false)
193        .bulk_memory(false)
194        .reference_types(false)
195        .multi_value(false)
196        .threads(false)
197        .exceptions(false)
198        .memory64(false);
199
200    // Set features based on the string values
201    for feature in feature_strings {
202        match feature.as_str() {
203            "simd" => {
204                features.simd(true);
205            }
206            "bulk-memory" => {
207                features.bulk_memory(true);
208            }
209            "reference-types" => {
210                features.reference_types(true);
211            }
212            "multi-value" => {
213                features.multi_value(true);
214            }
215            "threads" => {
216                features.threads(true);
217            }
218            "exception-handling" => {
219                features.exceptions(true);
220            }
221            "memory64" => {
222                features.memory64(true);
223            }
224            "relaxed-simd" => {
225                features.relaxed_simd(true);
226            }
227            "wide-arithmetic" => {
228                features.wide_arithmetic(true);
229            }
230            "extended-const" => {
231                features.extended_const(true);
232            }
233            "tail-call" => {
234                features.tail_call(true);
235            }
236            "multi-memory" => {
237                features.multi_memory(true);
238            }
239            // Ignore unrecognized features
240            _ => {}
241        }
242    }
243
244    features
245}
246
247#[cfg(all(test, feature = "execution", not(feature = "authoring")))]
248mod execution_only_tests {
249    use webc::ContainerError;
250
251    use super::from_bytes;
252    use crate::WasmerPackageError;
253
254    #[test]
255    fn rejects_authoring_formats() {
256        let error = from_bytes(vec![0x1f, 0x8b]).unwrap_err();
257        assert!(matches!(
258            error,
259            WasmerPackageError::ContainerError(ContainerError::FeatureNotEnabled {
260                feature: "authoring"
261            })
262        ));
263    }
264}