use alloc::vec::Vec;
use crate::binary::leb128::{self, Cursor};
use crate::error::{ByteOffset, DecodeContext, DecodeError, DecodeErrorKind};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum SectionId {
Custom = 0,
Type = 1,
Import = 2,
Function = 3,
Table = 4,
Memory = 5,
Global = 6,
Export = 7,
Start = 8,
Element = 9,
Code = 10,
Data = 11,
DataCount = 12,
}
impl SectionId {
pub fn from_byte(byte: u8) -> Option<Self> {
match byte {
0 => Some(SectionId::Custom),
1 => Some(SectionId::Type),
2 => Some(SectionId::Import),
3 => Some(SectionId::Function),
4 => Some(SectionId::Table),
5 => Some(SectionId::Memory),
6 => Some(SectionId::Global),
7 => Some(SectionId::Export),
8 => Some(SectionId::Start),
9 => Some(SectionId::Element),
10 => Some(SectionId::Code),
11 => Some(SectionId::Data),
12 => Some(SectionId::DataCount),
_ => None,
}
}
pub fn name(self) -> &'static str {
match self {
SectionId::Custom => "custom",
SectionId::Type => "type",
SectionId::Import => "import",
SectionId::Function => "function",
SectionId::Table => "table",
SectionId::Memory => "memory",
SectionId::Global => "global",
SectionId::Export => "export",
SectionId::Start => "start",
SectionId::Element => "element",
SectionId::Code => "code",
SectionId::Data => "data",
SectionId::DataCount => "datacount",
}
}
}
#[derive(Debug, Clone)]
pub struct RawSection<'a> {
pub id: SectionId,
pub offset: usize,
pub data: &'a [u8],
}
const WASM_MAGIC: [u8; 4] = [0x00, 0x61, 0x73, 0x6D];
const WASM_VERSION: [u8; 4] = [0x01, 0x00, 0x00, 0x00];
pub fn parse_preamble<'a>(cursor: &mut Cursor<'a>) -> Result<(), DecodeError> {
let magic = cursor.read_bytes(4).map_err(|_| DecodeError {
offset: ByteOffset(0),
context: DecodeContext::Magic,
kind: DecodeErrorKind::UnexpectedEof,
})?;
if magic != WASM_MAGIC {
return Err(DecodeError {
offset: ByteOffset(0),
context: DecodeContext::Magic,
kind: DecodeErrorKind::InvalidMagic,
});
}
let version = cursor.read_bytes(4).map_err(|_| DecodeError {
offset: ByteOffset(4),
context: DecodeContext::Version,
kind: DecodeErrorKind::UnexpectedEof,
})?;
if version != WASM_VERSION {
let found = u32::from_le_bytes([version[0], version[1], version[2], version[3]]);
return Err(DecodeError {
offset: ByteOffset(4),
context: DecodeContext::Version,
kind: DecodeErrorKind::UnsupportedVersion { found },
});
}
Ok(())
}
fn section_order(id: SectionId) -> u8 {
match id {
SectionId::Custom => 0,
SectionId::Type => 1,
SectionId::Import => 2,
SectionId::Function => 3,
SectionId::Table => 4,
SectionId::Memory => 5,
SectionId::Global => 6,
SectionId::Export => 7,
SectionId::Start => 8,
SectionId::Element => 9,
SectionId::Code => 10,
SectionId::Data => 11,
SectionId::DataCount => 12,
}
}
fn sections_in_valid_order(prev: SectionId, current: SectionId) -> bool {
let prev_order = section_order(prev);
let current_order = section_order(current);
current_order >= prev_order
|| matches!(
(prev, current),
(SectionId::Element, SectionId::DataCount)
| (SectionId::DataCount, SectionId::Code)
| (SectionId::Data, SectionId::DataCount)
)
}
fn validate_custom_section_name(data: &[u8], base_offset: usize) -> Result<(), DecodeError> {
let mut cursor = Cursor::new(data);
let name_len = leb128::decode_u32(&mut cursor).map_err(|mut e| {
e.context = DecodeContext::SectionBody { id: 0 };
e.offset = ByteOffset(base_offset + e.offset.0);
e
})? as usize;
let name_offset = cursor.position();
let name = cursor.read_bytes(name_len).map_err(|_| DecodeError {
offset: ByteOffset(base_offset + name_offset),
context: DecodeContext::SectionBody { id: 0 },
kind: DecodeErrorKind::UnexpectedEof,
})?;
core::str::from_utf8(name).map_err(|_| DecodeError {
offset: ByteOffset(base_offset + name_offset),
context: DecodeContext::SectionBody { id: 0 },
kind: DecodeErrorKind::InvalidUtf8,
})?;
Ok(())
}
pub fn parse_sections<'a>(cursor: &mut Cursor<'a>) -> Result<Vec<RawSection<'a>>, DecodeError> {
let mut sections = Vec::new();
let mut last_non_custom: Option<(u8, u8)> = None;
while !cursor.is_empty() {
let id_offset = cursor.position();
let id_byte = cursor.read_byte().map_err(|_| DecodeError {
offset: ByteOffset(id_offset),
context: DecodeContext::SectionHeader,
kind: DecodeErrorKind::UnexpectedEof,
})?;
let id = SectionId::from_byte(id_byte).ok_or(DecodeError {
offset: ByteOffset(id_offset),
context: DecodeContext::SectionHeader,
kind: DecodeErrorKind::UnknownSectionId { id: id_byte },
})?;
let size = leb128::decode_u32(cursor).map_err(|mut e| {
e.context = DecodeContext::SectionHeader;
e
})?;
let content_offset = cursor.position();
if content_offset + size as usize > cursor.position() + cursor.remaining().len() {
return Err(DecodeError {
offset: ByteOffset(id_offset),
context: DecodeContext::SectionHeader,
kind: DecodeErrorKind::SectionOverflow,
});
}
if id != SectionId::Custom {
let current_order = section_order(id);
if let Some((prev_id, _prev_order)) = last_non_custom {
if id_byte == prev_id {
return Err(DecodeError {
offset: ByteOffset(id_offset),
context: DecodeContext::SectionHeader,
kind: DecodeErrorKind::DuplicateSection { id: id_byte },
});
}
if !sections_in_valid_order(
SectionId::from_byte(prev_id).expect("known section id"),
id,
) {
return Err(DecodeError {
offset: ByteOffset(id_offset),
context: DecodeContext::SectionHeader,
kind: DecodeErrorKind::SectionOutOfOrder {
prev: prev_id,
current: id_byte,
},
});
}
}
last_non_custom = Some((id_byte, current_order));
}
let data = cursor.read_bytes(size as usize).map_err(|_| DecodeError {
offset: ByteOffset(content_offset),
context: DecodeContext::SectionBody { id: id_byte },
kind: DecodeErrorKind::SectionOverflow,
})?;
if id == SectionId::Custom {
validate_custom_section_name(data, content_offset)?;
}
sections.push(RawSection {
id,
offset: content_offset,
data,
});
}
Ok(sections)
}
#[cfg(test)]
mod tests {
use super::*;
const MINIMAL_MODULE: [u8; 8] = [
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, ];
#[test]
fn parse_minimal_module_preamble() {
let mut cursor = Cursor::new(&MINIMAL_MODULE);
parse_preamble(&mut cursor).unwrap();
assert!(cursor.is_empty());
}
#[test]
fn reject_bad_magic() {
let data = [0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00];
let mut cursor = Cursor::new(&data);
let err = parse_preamble(&mut cursor).unwrap_err();
assert_eq!(err.kind, DecodeErrorKind::InvalidMagic);
}
#[test]
fn reject_bad_version() {
let data = [0x00, 0x61, 0x73, 0x6D, 0x02, 0x00, 0x00, 0x00];
let mut cursor = Cursor::new(&data);
let err = parse_preamble(&mut cursor).unwrap_err();
assert!(matches!(
err.kind,
DecodeErrorKind::UnsupportedVersion { found: 2 }
));
}
#[test]
fn parse_empty_sections() {
let mut cursor = Cursor::new(&MINIMAL_MODULE);
parse_preamble(&mut cursor).unwrap();
let sections = parse_sections(&mut cursor).unwrap();
assert!(sections.is_empty());
}
#[test]
fn parse_single_type_section() {
let data = [
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x01, 0x02, 0xAA, 0xBB, ];
let mut cursor = Cursor::new(&data);
parse_preamble(&mut cursor).unwrap();
let sections = parse_sections(&mut cursor).unwrap();
assert_eq!(sections.len(), 1);
assert_eq!(sections[0].id, SectionId::Type);
assert_eq!(sections[0].data, &[0xAA, 0xBB]);
}
#[test]
fn parse_multiple_sections_in_order() {
let data = [
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0xFF, 0x03, 0x01, 0xEE, 0x07, 0x01, 0xDD, ];
let mut cursor = Cursor::new(&data);
parse_preamble(&mut cursor).unwrap();
let sections = parse_sections(&mut cursor).unwrap();
assert_eq!(sections.len(), 3);
assert_eq!(sections[0].id, SectionId::Type);
assert_eq!(sections[1].id, SectionId::Function);
assert_eq!(sections[2].id, SectionId::Export);
}
#[test]
fn reject_duplicate_section() {
let data = [
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0xFF, 0x01, 0x01, 0xEE, ];
let mut cursor = Cursor::new(&data);
parse_preamble(&mut cursor).unwrap();
let err = parse_sections(&mut cursor).unwrap_err();
assert!(matches!(
err.kind,
DecodeErrorKind::DuplicateSection { id: 1 }
));
}
#[test]
fn reject_out_of_order_sections() {
let data = [
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x03, 0x01, 0xFF, 0x01, 0x01, 0xEE, ];
let mut cursor = Cursor::new(&data);
parse_preamble(&mut cursor).unwrap();
let err = parse_sections(&mut cursor).unwrap_err();
assert!(matches!(
err.kind,
DecodeErrorKind::SectionOutOfOrder {
prev: 3,
current: 1
}
));
}
#[test]
fn allow_data_count_before_code_and_data() {
let data = [
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x09, 0x01, 0xAA, 0x0C, 0x01, 0xBB, 0x0A, 0x01, 0xCC, 0x0B, 0x01, 0xDD, ];
let mut cursor = Cursor::new(&data);
parse_preamble(&mut cursor).unwrap();
let sections = parse_sections(&mut cursor).unwrap();
assert_eq!(sections.len(), 4);
assert_eq!(sections[0].id, SectionId::Element);
assert_eq!(sections[1].id, SectionId::DataCount);
assert_eq!(sections[2].id, SectionId::Code);
assert_eq!(sections[3].id, SectionId::Data);
}
#[test]
fn custom_sections_allowed_anywhere() {
let data = [
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0xAA, 0x00, 0x01, 0x00, 0x03, 0x01, 0xCC, 0x00, 0x01, 0x00, ];
let mut cursor = Cursor::new(&data);
parse_preamble(&mut cursor).unwrap();
let sections = parse_sections(&mut cursor).unwrap();
assert_eq!(sections.len(), 5);
assert_eq!(sections[0].id, SectionId::Custom);
assert_eq!(sections[1].id, SectionId::Type);
assert_eq!(sections[2].id, SectionId::Custom);
assert_eq!(sections[3].id, SectionId::Function);
assert_eq!(sections[4].id, SectionId::Custom);
}
#[test]
fn reject_section_overflow() {
let data = [
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x01, 0xFF, 0x01, ];
let mut cursor = Cursor::new(&data);
parse_preamble(&mut cursor).unwrap();
let err = parse_sections(&mut cursor).unwrap_err();
assert!(matches!(err.kind, DecodeErrorKind::SectionOverflow));
}
#[test]
fn reject_invalid_utf8_custom_section_name() {
let data = [
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x80, ];
let mut cursor = Cursor::new(&data);
parse_preamble(&mut cursor).unwrap();
let err = parse_sections(&mut cursor).unwrap_err();
assert!(matches!(err.kind, DecodeErrorKind::InvalidUtf8));
assert!(matches!(err.context, DecodeContext::SectionBody { id: 0 }));
}
}