/ parameters / src / macros.rs
macros.rs
  1  // Copyright (c) 2019-2025 Alpha-Delta Network Inc.
  2  // This file is part of the alphavm library.
  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  #[macro_export]
 17  macro_rules! checksum {
 18      ($bytes: expr) => {{
 19          use sha2::Digest;
 20          hex::encode(&sha2::Sha256::digest($bytes))
 21      }};
 22  }
 23  
 24  #[macro_export]
 25  macro_rules! checksum_error {
 26      ($expected: expr, $candidate: expr) => {
 27          Err($crate::errors::ParameterError::ChecksumMismatch($expected, $candidate))
 28      };
 29  }
 30  
 31  #[macro_export]
 32  macro_rules! remove_file {
 33      ($filepath:expr) => {
 34          // Safely remove the corrupt file, if it exists.
 35          #[cfg(not(feature = "wasm"))]
 36          if std::path::PathBuf::from(&$filepath).exists() {
 37              match std::fs::remove_file(&$filepath) {
 38                  Ok(()) => println!("Removed {:?}. Please retry the command.", $filepath),
 39                  Err(err) => eprintln!("Failed to remove {:?}: {err}", $filepath),
 40              }
 41          }
 42      };
 43  }
 44  
 45  macro_rules! impl_store_and_remote_fetch {
 46      () => {
 47          #[cfg(not(feature = "wasm"))]
 48          fn store_bytes(buffer: &[u8], file_path: &std::path::Path) -> Result<(), $crate::errors::ParameterError> {
 49              use alphavm_utilities::Write;
 50  
 51              #[cfg(not(feature = "no_std_out"))]
 52              {
 53                  use colored::*;
 54                  let output = format!("{:>15} - Storing file in {:?}", "Installation", file_path);
 55                  println!("{}", output.dimmed());
 56              }
 57  
 58              // Ensure the folders up to the file path all exist.
 59              let mut directory_path = file_path.to_path_buf();
 60              directory_path.pop();
 61              let _ = std::fs::create_dir_all(directory_path)?;
 62  
 63              // Attempt to write the parameter buffer to a file.
 64              match std::fs::File::create(file_path) {
 65                  Ok(mut file) => file.write_all(&buffer)?,
 66                  Err(error) => eprintln!("{}", error),
 67              }
 68              Ok(())
 69          }
 70  
 71          #[cfg(all(not(feature = "wasm"), not(target_env = "sgx")))]
 72          fn remote_fetch(buffer: &mut Vec<u8>, url: &str) -> Result<(), $crate::errors::ParameterError> {
 73              let mut easy = curl::easy::Easy::new();
 74              easy.follow_location(true)?;
 75              easy.url(url)?;
 76  
 77              #[cfg(not(feature = "no_std_out"))]
 78              {
 79                  use colored::*;
 80  
 81                  let output = format!("{:>15} - Downloading \"{}\"", "Installation", url);
 82                  println!("{}", output.dimmed());
 83  
 84                  easy.progress(true)?;
 85                  easy.progress_function(|total_download, current_download, _, _| {
 86                      let percent = (current_download / total_download) * 100.0;
 87                      let size_in_megabytes = total_download as u64 / 1_048_576;
 88                      let output =
 89                          format!("\r{:>15} - {:.2}% complete ({:#} MB total)", "Installation", percent, size_in_megabytes);
 90                      print!("{}", output.dimmed());
 91                      true
 92                  })?;
 93              }
 94  
 95              let mut transfer = easy.transfer();
 96              transfer.write_function(|data| {
 97                  buffer.extend_from_slice(data);
 98                  Ok(data.len())
 99              })?;
100              Ok(transfer.perform()?)
101          }
102  
103          #[cfg(feature = "wasm")]
104          fn remote_fetch(url: &str) -> Result<Vec<u8>, $crate::errors::ParameterError> {
105              // Use the browser's XmlHttpRequest object to download the parameter file synchronously.
106              //
107              // This method blocks the event loop while the parameters are downloaded, and should be
108              // executed in a web worker to prevent the main browser window from freezing.
109              let xhr = web_sys::XmlHttpRequest::new().map_err(|_| {
110                  $crate::errors::ParameterError::Wasm("Download failed - XMLHttpRequest object not found".to_string())
111              })?;
112  
113              // XmlHttpRequest if specified as synchronous cannot use the responseType property. It
114              // cannot thus download bytes directly and enforces a text encoding. To get back the
115              // original binary, a charset that does not corrupt the original bytes must be used.
116              xhr.override_mime_type("octet/binary; charset=ISO-8859-5").unwrap();
117  
118              // Initialize and send the request.
119              xhr.open_with_async("GET", url, false).map_err(|_| {
120                  $crate::errors::ParameterError::Wasm(
121                      "Download failed - This browser does not support synchronous requests".to_string(),
122                  )
123              })?;
124              xhr.send()
125                  .map_err(|_| $crate::errors::ParameterError::Wasm("Download failed - XMLHttpRequest failed".to_string()))?;
126  
127              // Wait for the response in a blocking fashion.
128              if xhr.response().is_ok() && xhr.status().unwrap() == 200 {
129                  // Get the text from the response.
130                  let rust_text = xhr
131                      .response_text()
132                      .map_err(|_| $crate::errors::ParameterError::Wasm("XMLHttpRequest failed".to_string()))?
133                      .ok_or($crate::errors::ParameterError::Wasm(
134                          "The request was successful but no parameters were received".to_string(),
135                      ))?;
136  
137                  // Re-encode the text back into bytes using the chosen encoding.
138                  use encoding::Encoding;
139                  encoding::all::ISO_8859_5
140                      .encode(&rust_text, encoding::EncoderTrap::Strict)
141                      .map_err(|_| $crate::errors::ParameterError::Wasm("Parameter decoding failed".to_string()))
142              } else {
143                  Err($crate::errors::ParameterError::Wasm("Download failed - XMLHttpRequest failed".to_string()))
144              }
145          }
146      };
147  }
148  
149  macro_rules! impl_load_bytes_logic_local {
150      ($filepath: expr, $buffer: expr, $expected_size: expr, $expected_checksum: expr) => {
151          // Ensure the size matches.
152          if $expected_size != $buffer.len() {
153              remove_file!($filepath);
154              return Err($crate::errors::ParameterError::SizeMismatch($expected_size, $buffer.len()));
155          }
156  
157          // Ensure the checksum matches.
158          let candidate_checksum = checksum!($buffer);
159          if $expected_checksum != candidate_checksum {
160              return checksum_error!($expected_checksum, candidate_checksum);
161          }
162  
163          return Ok($buffer.to_vec());
164      };
165  }
166  
167  macro_rules! impl_load_bytes_logic_remote {
168      ($remote_url: expr, $local_dir: expr, $filename: expr, $metadata: expr, $expected_checksum: expr, $expected_size: expr) => {
169          cfg_if::cfg_if! {
170              if #[cfg(all(feature = "filesystem", not(feature="wasm")))] {
171                  // Compose the correct file path for the parameter file.
172                  let mut file_path = alphastd::alpha_dir();
173                  file_path.push($local_dir);
174                  file_path.push($filename);
175  
176                  let buffer = if file_path.exists() {
177                      // Attempts to load the parameter file locally with an absolute path.
178                      std::fs::read(&file_path)?
179                  } else {
180                      // Downloads the missing parameters and stores it in the local directory for use.
181                      #[cfg(not(feature = "no_std_out"))]
182                      {
183                          use colored::*;
184                          let path = format!("(in {:?})", file_path);
185                          eprintln!(
186                              "\n⚠️  \"{}\" does not exist. Downloading and storing it {}.\n",
187                              $filename, path.dimmed()
188                          );
189                      }
190  
191                      // Load remote file
192                      cfg_if::cfg_if!{
193                          if #[cfg(all(not(feature = "wasm"), not(target_env = "sgx")))] {
194                              let url = format!("{}/{}", $remote_url, $filename);
195                              let mut buffer = vec![];
196                              Self::remote_fetch(&mut buffer, &url)?;
197  
198                              // Ensure the checksum matches.
199                              let candidate_checksum = checksum!(&buffer);
200                              if $expected_checksum != candidate_checksum {
201                                  return checksum_error!($expected_checksum, candidate_checksum)
202                              }
203  
204                              match Self::store_bytes(&buffer, &file_path) {
205                                  Ok(()) => buffer,
206                                  Err(_) => {
207                                      eprintln!(
208                                          "\n❗ Error - Failed to store \"{}\" locally. Please download this file manually and ensure it is stored in {:?}.\n",
209                                          $filename, file_path
210                                      );
211                                      buffer
212                                  }
213                              }
214                          } else {
215                              return Err($crate::errors::ParameterError::RemoteFetchDisabled);
216                          }
217                      }
218                  };
219  
220                  // Ensure the size matches.
221                  if $expected_size != buffer.len() {
222                      remove_file!(file_path);
223                      return Err($crate::errors::ParameterError::SizeMismatch($expected_size, buffer.len()));
224                  }
225  
226                  // Ensure the checksum matches.
227                  let candidate_checksum = checksum!(buffer.as_slice());
228                  if $expected_checksum != candidate_checksum {
229                      return checksum_error!($expected_checksum, candidate_checksum)
230                  }
231                  return Ok(buffer);
232              } else {
233                  cfg_if::cfg_if! {
234                      if #[cfg(feature = "wasm")] {
235                          let url = format!("{}/{}", $remote_url, $filename);
236                          let buffer = Self::remote_fetch(&url)?;
237  
238                          // Ensure the size matches.
239                          if $expected_size != buffer.len() {
240                              remove_file!(file_path);
241                              return Err($crate::errors::ParameterError::SizeMismatch($expected_size, buffer.len()));
242                          }
243  
244                          // Ensure the checksum matches.
245                          let candidate_checksum = checksum!(&buffer);
246                          if $expected_checksum != candidate_checksum {
247                              return checksum_error!($expected_checksum, candidate_checksum)
248                          }
249  
250                          return Ok(buffer)
251                      } else {
252                          return Err($crate::errors::ParameterError::FilesystemDisabled);
253                      }
254                  }
255              }
256          }
257      }
258  }
259  
260  #[macro_export]
261  macro_rules! impl_local {
262      ($name: ident, $local_dir: expr, $fname: tt, "usrs") => {
263          #[derive(Clone, Debug, PartialEq, Eq)]
264          pub struct $name;
265  
266          impl $name {
267              pub const METADATA: &'static str = include_str!(concat!($local_dir, $fname, ".metadata"));
268  
269              pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
270                  let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
271                  let expected_checksum: String = metadata["checksum"].as_str().expect("Failed to parse checksum").to_string();
272                  let expected_size: usize = metadata["size"].to_string().parse().expect("Failed to retrieve the file size");
273  
274                  let _filepath = concat!($local_dir, $fname, ".", "usrs");
275                  let buffer = include_bytes!(concat!($local_dir, $fname, ".", "usrs"));
276  
277                  impl_load_bytes_logic_local!(_filepath, buffer, expected_size, expected_checksum);
278              }
279          }
280  
281          paste::item! {
282              #[cfg(test)]
283              #[test]
284              fn [< test_ $fname _usrs >]() {
285                  assert!($name::load_bytes().is_ok());
286              }
287          }
288      };
289      ($name: ident, $local_dir: expr, $fname: tt, $ftype: tt, $credits_version: tt) => {
290          #[derive(Clone, Debug, PartialEq, Eq)]
291          pub struct $name;
292  
293          impl $name {
294              pub const METADATA: &'static str = include_str!(concat!($local_dir, $credits_version, "/", $fname, ".metadata"));
295  
296              pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
297                  let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
298                  let expected_checksum: String =
299                      metadata[concat!($ftype, "_checksum")].as_str().expect("Failed to parse checksum").to_string();
300                  let expected_size: usize =
301                      metadata[concat!($ftype, "_size")].to_string().parse().expect("Failed to retrieve the file size");
302  
303                  let _filepath = concat!($local_dir, $credits_version, "/", $fname, ".", $ftype);
304                  let buffer = include_bytes!(concat!($local_dir, $credits_version, "/", $fname, ".", $ftype));
305  
306                  impl_load_bytes_logic_local!(_filepath, buffer, expected_size, expected_checksum);
307              }
308          }
309  
310          paste::item! {
311              #[cfg(test)]
312              #[test]
313              fn [< test_ $credits_version _ $fname _ $ftype >]() {
314                  assert!($name::load_bytes().is_ok());
315              }
316          }
317      };
318  }
319  
320  #[macro_export]
321  macro_rules! impl_remote {
322      ($name: ident, $remote_url: expr, $local_dir: expr, $fname: tt, "usrs") => {
323          pub struct $name;
324  
325          impl $name {
326              pub const METADATA: &'static str = include_str!(concat!($local_dir, $fname, ".metadata"));
327  
328              impl_store_and_remote_fetch!();
329  
330              pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
331                  let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
332                  let expected_checksum: String = metadata["checksum"].as_str().expect("Failed to parse checksum").to_string();
333                  let expected_size: usize = metadata["size"].to_string().parse().expect("Failed to retrieve the file size");
334  
335                  // Construct the versioned filename.
336                  let filename = match expected_checksum.get(0..7) {
337                      Some(sum) => format!("{}.{}.{}", $fname, "usrs", sum),
338                      _ => format!("{}.{}", $fname, "usrs"),
339                  };
340  
341                  impl_load_bytes_logic_remote!($remote_url, $local_dir, &filename, metadata, expected_checksum, expected_size);
342              }
343          }
344          paste::item! {
345              #[cfg(test)]
346              #[test]
347              fn [< test_ $fname _usrs >]() {
348                  assert!($name::load_bytes().is_ok());
349              }
350          }
351      };
352      ($name: ident, $remote_url: expr, $local_dir: expr, $fname: tt, $ftype: tt, $credits_version: tt) => {
353          pub struct $name;
354  
355          impl $name {
356              pub const METADATA: &'static str = include_str!(concat!($local_dir, $credits_version, "/", $fname, ".metadata"));
357  
358              impl_store_and_remote_fetch!();
359  
360              pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
361                  let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
362                  let expected_checksum: String =
363                      metadata[concat!($ftype, "_checksum")].as_str().expect("Failed to parse checksum").to_string();
364                  let expected_size: usize =
365                      metadata[concat!($ftype, "_size")].to_string().parse().expect("Failed to retrieve the file size");
366  
367                  // Construct the versioned filename.
368                  let filename = match expected_checksum.get(0..7) {
369                      Some(sum) => format!("{}.{}.{}", $fname, $ftype, sum),
370                      _ => format!("{}.{}", $fname, $ftype),
371                  };
372  
373                  impl_load_bytes_logic_remote!($remote_url, $local_dir, &filename, metadata, expected_checksum, expected_size);
374              }
375  
376              #[cfg(feature = "wasm")]
377              /// Verify external bytes.
378              pub fn verify_bytes(buffer: &[u8]) -> Result<(), $crate::errors::ParameterError> {
379                  let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
380                  let expected_checksum: String =
381                      metadata[concat!($ftype, "_checksum")].as_str().expect("Failed to parse checksum").to_string();
382                  let expected_size: usize =
383                      metadata[concat!($ftype, "_size")].to_string().parse().expect("Failed to retrieve the file size");
384  
385                  // Ensure the size matches.
386                  if buffer.len() != expected_size {
387                      return Err($crate::errors::ParameterError::SizeMismatch(expected_size, buffer.len()));
388                  }
389  
390                  // Ensure the checksum matches.
391                  let candidate_checksum = checksum!(buffer);
392                  if expected_checksum != candidate_checksum {
393                      return checksum_error!(expected_checksum, candidate_checksum);
394                  }
395                  Ok(())
396              }
397          }
398  
399          paste::item! {
400              #[cfg(test)]
401              #[test]
402              fn [< test_ $credits_version _ $fname _ $ftype >]() {
403                  assert!($name::load_bytes().is_ok());
404              }
405          }
406      };
407  }