#![cfg_attr(coverage_nightly, coverage(off))]
use anyhow::Result;
use std::path::Path;
use super::types::WasmMetrics;
#[derive(Debug, Clone)]
pub struct WasmAnalysis {
pub sections: Vec<WasmSection>,
}
#[derive(Debug, Clone)]
pub struct WasmSection {
pub id: u8,
pub size: usize,
}
pub struct WasmBinaryAnalyzer {
max_file_size: usize,
}
impl WasmBinaryAnalyzer {
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new() -> Self {
Self {
max_file_size: 10 * 1024 * 1024, }
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub async fn analyze_file(&self, file_path: &Path) -> Result<WasmMetrics> {
let content = tokio::fs::read(file_path).await?;
if content.len() > self.max_file_size {
return Err(anyhow::anyhow!("File too large: {} bytes", content.len()));
}
if content.len() < 8 || &content[0..4] != b"\0asm" {
return Err(anyhow::anyhow!("Invalid WASM file format"));
}
let metrics = WasmMetrics {
function_count: count_occurrences(&content, &[0x01]), import_count: count_occurrences(&content, &[0x02]), export_count: count_occurrences(&content, &[0x07]), linear_memory_pages: u32::from(content.len() > 1000),
..Default::default()
};
Ok(metrics)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn analyze_bytes(&self, data: &[u8]) -> Result<WasmAnalysis> {
if data.len() < 8 {
return Err(anyhow::anyhow!("File too small to be valid WASM"));
}
if &data[0..4] != b"\0asm" {
return Err(anyhow::anyhow!("Invalid WASM magic number"));
}
let mut sections = Vec::new();
let mut pos = 8;
while pos < data.len() {
if pos + 2 > data.len() {
break;
}
let section_id = data[pos];
pos += 1;
let mut size = 0u64;
let mut shift = 0;
loop {
if pos >= data.len() {
break;
}
let byte = data[pos];
pos += 1;
size |= u64::from(byte & 0x7F) << shift;
if byte & 0x80 == 0 {
break;
}
shift += 7;
if shift > 35 {
return Err(anyhow::anyhow!("Invalid LEB128 encoding"));
}
}
sections.push(WasmSection {
id: section_id,
size: size as usize,
});
pos += size as usize;
if pos > data.len() {
break;
}
}
Ok(WasmAnalysis { sections })
}
}
impl Default for WasmBinaryAnalyzer {
fn default() -> Self {
Self::new()
}
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn count_occurrences(haystack: &[u8], needle: &[u8]) -> u32 {
let mut count = 0;
let mut pos = 0;
while pos + needle.len() <= haystack.len() {
if &haystack[pos..pos + needle.len()] == needle {
count += 1;
pos += needle.len();
} else {
pos += 1;
}
}
count
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
use tempfile::NamedTempFile;
use tokio::io::AsyncWriteExt;
#[tokio::test]
async fn test_wasm_binary_analyzer() {
let analyzer = WasmBinaryAnalyzer::new();
let temp_file = NamedTempFile::new().unwrap();
let mut file = tokio::fs::File::create(temp_file.path()).await.unwrap();
file.write_all(b"\0asm\x01\x00\x00\x00").await.unwrap();
file.flush().await.unwrap();
let result = analyzer.analyze_file(temp_file.path()).await;
assert!(result.is_ok());
let metrics = result.unwrap();
assert_eq!(metrics.function_count, 1);
}
#[test]
fn test_count_occurrences() {
let data = b"\x01\x02\x01\x03\x01";
let count = count_occurrences(data, &[0x01]);
assert_eq!(count, 3);
}
#[test]
fn test_count_occurrences_empty_haystack() {
assert_eq!(count_occurrences(b"", &[0x01]), 0);
}
#[test]
fn test_count_occurrences_needle_larger_than_haystack() {
assert_eq!(count_occurrences(b"hi", b"hello"), 0);
}
#[test]
fn test_count_occurrences_no_match() {
assert_eq!(count_occurrences(b"hello world", b"xyz"), 0);
}
#[test]
fn test_count_occurrences_multi_byte() {
let data = b"hello world hello";
assert_eq!(count_occurrences(data, b"hello"), 2);
}
#[test]
fn test_wasm_binary_analyzer_default() {
let analyzer = WasmBinaryAnalyzer::default();
assert_eq!(analyzer.max_file_size, 10 * 1024 * 1024);
}
#[test]
fn test_analyze_bytes_valid() {
let analyzer = WasmBinaryAnalyzer::new();
let data = b"\0asm\x01\x00\x00\x00";
let result = analyzer.analyze_bytes(data);
assert!(result.is_ok());
let analysis = result.unwrap();
assert!(analysis.sections.is_empty());
}
#[test]
fn test_analyze_bytes_with_section() {
let analyzer = WasmBinaryAnalyzer::new();
let data = b"\0asm\x01\x00\x00\x00\x01\x00";
let result = analyzer.analyze_bytes(data);
assert!(result.is_ok());
let analysis = result.unwrap();
assert_eq!(analysis.sections.len(), 1);
assert_eq!(analysis.sections[0].id, 1);
assert_eq!(analysis.sections[0].size, 0);
}
#[test]
fn test_analyze_bytes_too_small() {
let analyzer = WasmBinaryAnalyzer::new();
let data = b"\0asm";
let result = analyzer.analyze_bytes(data);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("too small"));
}
#[test]
fn test_analyze_bytes_invalid_magic() {
let analyzer = WasmBinaryAnalyzer::new();
let data = b"invalid\x00";
let result = analyzer.analyze_bytes(data);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("magic number"));
}
#[test]
fn test_wasm_section_clone() {
let section = WasmSection { id: 5, size: 100 };
let cloned = section.clone();
assert_eq!(cloned.id, 5);
assert_eq!(cloned.size, 100);
}
#[test]
fn test_wasm_analysis_clone() {
let analysis = WasmAnalysis {
sections: vec![WasmSection { id: 1, size: 10 }],
};
let cloned = analysis.clone();
assert_eq!(cloned.sections.len(), 1);
}
#[tokio::test]
async fn test_analyze_file_invalid_format() {
let analyzer = WasmBinaryAnalyzer::new();
let temp_file = NamedTempFile::new().unwrap();
let mut file = tokio::fs::File::create(temp_file.path()).await.unwrap();
file.write_all(b"not wasm content").await.unwrap();
file.flush().await.unwrap();
let result = analyzer.analyze_file(temp_file.path()).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Invalid WASM"));
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}