macros.rs
1 // Copyright (c) 2019-2025 Alpha-Delta Network Inc. 2 // This file is part of the deltavm 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 deltavm_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 }