#[derive(Clone, Debug)]
pub enum ModuleValidationError {
WebProxyMissingSyscalls { syscalls: Vec<String> },
WcgiMissingSyscalls { syscalls: Vec<String> },
}
impl ModuleValidationError {
pub fn explain(&self) -> String {
match self {
Self::WebProxyMissingSyscalls { syscalls } => {
format!(
"web_proxy modules must read and write to sockets, \
but the module does not import the WASI functions required \
to do so. \\n
Missing functions: {}",
syscalls.join(", ")
)
}
Self::WcgiMissingSyscalls { syscalls } => {
format!(
"wcgi modules must read from stdin and write to stdout, \
but the module does not import the WASI functions required \
to do so. \n\
Missing functions: {}",
syscalls.join(", ")
)
}
}
}
}
impl std::fmt::Display for ModuleValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::WebProxyMissingSyscalls { syscalls } => {
write!(
f,
"Module does not import required syscalls: {}",
syscalls.join(", ")
)
}
Self::WcgiMissingSyscalls { syscalls } => {
write!(
f,
"Module does not import required syscalls: {}",
syscalls.join(", ")
)
}
}
}
}
impl std::error::Error for ModuleValidationError {}
#[derive(Clone, Debug)]
pub enum CapabilityValidationError {
NetworkCapabilityMissing,
}
impl CapabilityValidationError {
pub fn explain(&self) -> String {
match self {
Self::NetworkCapabilityMissing => {
"vpn_proxy requires that the deployment configuration has
a network capability."
.to_string()
}
}
}
}
impl std::fmt::Display for CapabilityValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NetworkCapabilityMissing => {
write!(f, "Configuration does not have a network capability",)
}
}
}
}
impl std::error::Error for CapabilityValidationError {}
#[allow(clippy::manual_flatten)]
pub fn validate_parse_module_webproxy(bytes: &[u8]) -> Result<(), ModuleValidationError> {
let mut sock_listen = false;
let mut sock_accept = false;
let mut sock_recv = false;
let mut sock_send = false;
for payload in wasmparser::Parser::new(0).parse_all(bytes) {
if let Ok(wasmparser::Payload::ImportSection(imports)) = payload {
for import in imports {
if let Ok(import) = import {
if !matches!(import.ty, wasmparser::TypeRef::Func(_)) {
continue;
}
if import.name.contains("sock_listen") {
sock_listen = true;
} else if import.name.contains("sock_accept") {
sock_accept = true;
} else if import.name.contains("sock_recv") {
sock_recv = true;
} else if import.name.contains("sock_send") {
sock_send = true;
}
}
}
}
}
let mut missing = Vec::new();
if !sock_listen {
}
if !sock_accept {
missing.push("sock_accept".to_string());
}
if !sock_recv {
missing.push("sock_recv".to_string());
}
if !sock_send {
missing.push("sock_send".to_string());
}
if missing.is_empty() {
Ok(())
} else {
Err(ModuleValidationError::WebProxyMissingSyscalls { syscalls: missing })
}
}
#[allow(clippy::manual_flatten)]
pub fn validate_parse_module_wcgi(bytes: &[u8]) -> Result<(), ModuleValidationError> {
let mut fd_read = false;
let mut fd_write = false;
let mut fd_pipe = false;
for payload in wasmparser::Parser::new(0).parse_all(bytes) {
if let Ok(wasmparser::Payload::ImportSection(imports)) = payload {
for import in imports {
if let Ok(import) = import {
if !matches!(import.ty, wasmparser::TypeRef::Func(_)) {
continue;
}
if import.name.contains("fd_read") {
fd_read = true;
} else if import.name.contains("fd_write") {
fd_write = true;
} else if import.name.contains("fd_pipe") {
fd_pipe = true;
}
}
}
}
}
let mut missing = Vec::new();
if !fd_read {
missing.push("fd_read".to_string());
}
if !(fd_write || fd_pipe) {
missing.push("fd_write|fd_pipe".to_string());
}
if missing.is_empty() {
Ok(())
} else {
Err(ModuleValidationError::WcgiMissingSyscalls { syscalls: missing })
}
}
#[derive(Clone, Debug)]
pub enum DeploymentValidationError {
Module(ModuleValidationError),
MissingCapability(CapabilityValidationError),
UnsupportedRunner(String),
}
impl std::fmt::Display for DeploymentValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Module(e) => e.fmt(f),
Self::MissingCapability(e) => e.fmt(f),
Self::UnsupportedRunner(e) => write!(f, "Unsupported runner: {}", e),
}
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::*;
fn root_path() -> PathBuf {
std::env::var("CARGO_MANIFEST_DIR")
.map(PathBuf::from)
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap()
.to_owned()
}
fn tests_path() -> PathBuf {
root_path().join("wasm-tests").join("compiled")
}
#[test]
fn test_validate_wcgi_valid() {
let path = tests_path().join("local").join("wcgi-hello.wasm");
let contents = std::fs::read(path).unwrap();
validate_parse_module_wcgi(&contents).unwrap();
}
#[test]
fn test_validate_wcgi_invalid() {
let path = tests_path().join("vendor").join("empty.wasm");
let contents = std::fs::read(path).unwrap();
let res = validate_parse_module_wcgi(&contents);
assert!(matches!(
res,
Err(ModuleValidationError::WcgiMissingSyscalls { .. })
));
}
#[test]
fn test_validate_webproxy_valid() {
let path = tests_path().join("vendor").join("static-web-server.wasm");
let contents = std::fs::read(path).unwrap();
validate_parse_module_webproxy(&contents).unwrap();
}
#[test]
fn test_validate_webproxy_invalid() {
let path = tests_path().join("local").join("wcgi-hello.wasm");
let contents = std::fs::read(path).unwrap();
let res = validate_parse_module_webproxy(&contents);
assert!(matches!(
res,
Err(ModuleValidationError::WebProxyMissingSyscalls { .. })
));
}
}