1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5use std::error::Error;
6
7pub const WASM_MAGIC_BYTES: [u8; 4] = [0x00, b'a', b's', b'm'];
9
10pub const WASM_VERSION_1_BYTES: [u8; 4] = [0x01, 0x00, 0x00, 0x00];
12
13pub const WASM_BINARY_HEADER_LEN: usize = 8;
15
16#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
18pub enum WasmBinaryFormat {
19 #[default]
21 CoreModule,
22}
23
24impl WasmBinaryFormat {
25 #[must_use]
27 pub const fn as_str(self) -> &'static str {
28 match self {
29 Self::CoreModule => "core-module",
30 }
31 }
32
33 #[must_use]
35 pub const fn magic_bytes(self) -> [u8; 4] {
36 match self {
37 Self::CoreModule => WASM_MAGIC_BYTES,
38 }
39 }
40
41 #[must_use]
43 pub const fn version_bytes(self) -> [u8; 4] {
44 match self {
45 Self::CoreModule => WASM_VERSION_1_BYTES,
46 }
47 }
48}
49
50impl fmt::Display for WasmBinaryFormat {
51 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
52 formatter.write_str(self.as_str())
53 }
54}
55
56#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
58pub enum WasmBinaryError {
59 TooShort,
61 InvalidMagic,
63 UnsupportedVersion,
65}
66
67impl fmt::Display for WasmBinaryError {
68 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
69 match self {
70 Self::TooShort => formatter.write_str("WebAssembly binary header is too short"),
71 Self::InvalidMagic => formatter.write_str("missing WebAssembly magic bytes"),
72 Self::UnsupportedVersion => {
73 formatter.write_str("unsupported WebAssembly binary version")
74 },
75 }
76 }
77}
78
79impl Error for WasmBinaryError {}
80
81#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
83pub struct WasmBinaryHeader {
84 version: u32,
85}
86
87impl WasmBinaryHeader {
88 #[must_use]
90 pub const fn new(version: u32) -> Self {
91 Self { version }
92 }
93
94 #[must_use]
96 pub const fn version(self) -> u32 {
97 self.version
98 }
99
100 #[must_use]
102 pub const fn is_version_1(self) -> bool {
103 self.version == 1
104 }
105
106 #[must_use]
108 pub const fn format(self) -> WasmBinaryFormat {
109 WasmBinaryFormat::CoreModule
110 }
111}
112
113#[must_use]
115pub fn has_wasm_magic(bytes: &[u8]) -> bool {
116 bytes.get(..WASM_MAGIC_BYTES.len()) == Some(WASM_MAGIC_BYTES.as_slice())
117}
118
119#[must_use]
121pub fn looks_like_wasm_binary(bytes: &[u8]) -> bool {
122 validate_wasm_header(bytes).is_ok()
123}
124
125pub fn validate_wasm_header(bytes: &[u8]) -> Result<WasmBinaryHeader, WasmBinaryError> {
127 if bytes.len() < WASM_BINARY_HEADER_LEN {
128 return Err(WasmBinaryError::TooShort);
129 }
130
131 if !has_wasm_magic(bytes) {
132 return Err(WasmBinaryError::InvalidMagic);
133 }
134
135 let version = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
136 if version != 1 {
137 return Err(WasmBinaryError::UnsupportedVersion);
138 }
139
140 Ok(WasmBinaryHeader::new(version))
141}
142
143pub fn parse_wasm_binary_header(bytes: &[u8]) -> Result<WasmBinaryHeader, WasmBinaryError> {
145 validate_wasm_header(bytes)
146}
147
148pub fn validate_wasm_binary_header(bytes: &[u8]) -> Result<WasmBinaryHeader, WasmBinaryError> {
150 validate_wasm_header(bytes)
151}
152
153#[cfg(test)]
154mod tests {
155 use super::{
156 WASM_MAGIC_BYTES, WASM_VERSION_1_BYTES, WasmBinaryError, WasmBinaryFormat, has_wasm_magic,
157 looks_like_wasm_binary, parse_wasm_binary_header, validate_wasm_binary_header,
158 validate_wasm_header,
159 };
160
161 #[test]
162 fn detects_magic_bytes() {
163 assert!(has_wasm_magic(&WASM_MAGIC_BYTES));
164 assert!(!has_wasm_magic(b"wat"));
165 }
166
167 #[test]
168 fn validates_version_one_header() {
169 let mut bytes = Vec::from(WASM_MAGIC_BYTES);
170 bytes.extend(WASM_VERSION_1_BYTES);
171 let header = validate_wasm_header(&bytes).expect("valid version-one header");
172
173 assert_eq!(header.version(), 1);
174 assert!(header.is_version_1());
175 assert_eq!(header.format(), WasmBinaryFormat::CoreModule);
176 assert_eq!(header.format().to_string(), "core-module");
177 assert_eq!(WasmBinaryFormat::CoreModule.magic_bytes(), WASM_MAGIC_BYTES);
178 assert_eq!(parse_wasm_binary_header(&bytes), Ok(header));
179 assert_eq!(validate_wasm_binary_header(&bytes), Ok(header));
180 assert!(looks_like_wasm_binary(&bytes));
181 }
182
183 #[test]
184 fn rejects_invalid_headers() {
185 assert_eq!(
186 validate_wasm_header(b"\0asm"),
187 Err(WasmBinaryError::TooShort)
188 );
189 assert_eq!(
190 validate_wasm_header(b"nope\x01\0\0\0"),
191 Err(WasmBinaryError::InvalidMagic)
192 );
193 assert_eq!(
194 validate_wasm_header(b"\0asm\x02\0\0\0"),
195 Err(WasmBinaryError::UnsupportedVersion)
196 );
197 }
198}