diff --git a/.circleci/config.yml b/.circleci/config.yml index 7643725..7a5073a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,44 +1,145 @@ -# Javascript Node CircleCI 2.0 configuration file -# -# Check https://circleci.com/docs/2.0/language-javascript/ for more details -# version: 2 jobs: - build: - docker: - # specify the version you desire here - - image: circleci/node:9 - - # Specify service dependencies here if necessary - # CircleCI maintains a library of pre-built images - # documented at https://circleci.com/docs/2.0/circleci-images/ - # - image: circleci/mongo:3.4.4 + # build-evm: + # docker: + # # specify the version you desire here + # - image: circleci/node:9 - working_directory: ~/repo + # # Specify service dependencies here if necessary + # # CircleCI maintains a library of pre-built images + # # documented at https://circleci.com/docs/2.0/circleci-images/ + # # - image: circleci/mongo:3.4.4 - steps: - - checkout + # working_directory: ~/repo - # Download and cache dependencies - - restore_cache: - keys: - - v1-dependencies-{{ checksum "evm1/package.json" }} - # fallback to using the latest cache if no exact match is found - - v1-dependencies- + # steps: + # - checkout + + # # Download and cache dependencies + # - restore_cache: + # keys: + # - v1-dependencies-{{ checksum "evm1/package.json" }} + # # fallback to using the latest cache if no exact match is found + # - v1-dependencies- + + # - run: cd evm1 && npm install + # - run: cd evm1 && chmod +x ./scripts/test.sh - - run: cd evm1 && npm install - - run: cd evm1 && chmod +x ./scripts/test.sh + # - save_cache: + # paths: + # - evm1/node_modules + # key: v1-dependencies-{{ checksum "evm1/package.json" }} + # # run tests! + # - run: + # name: Running Local EVM Instance + # command: ./evm1/node_modules/.bin/ganache-cli + # background: true + # - run: cd evm1 && ./node_modules/.bin/truffle test + build: + docker: + - image: centos:latest + working_directory: /tmp/my-project + steps: + - restore_cache: + keys: + - deps7-{{ .Branch }}-{{ .Revision }} + # - deps7-{{ .Branch }}-cargo-{{ checksum "kernel-ewasm/Cargo.lock" }} + - deps7-{{ .Branch }}- + - deps7- + - run: + name: Install native build prequisites + command: | + yum -y update + yum install -y systemd-devel git make gcc-c++ gcc file binutils + curl -L "https://cmake.org/files/v3.12/cmake-3.12.0-Linux-x86_64.tar.gz" -o cmake.tar.gz + tar -xzf cmake.tar.gz + cp -r cmake-3.12.0-Linux-x86_64/* /usr/ + echo "PATH=/root/.cargo/bin:$PATH" >> ~/.profile + # only update cargo if it is not installed + if [ ! -d /root/.rustup ] + then + echo "Installing rustup" + curl https://sh.rustup.rs -sSf | sh -s -- -y + . ~/.profile + rustup default stable + else + echo "rustup already installed" + . ~/.profile + fi + RUST_BACKTRACE=1 + rustc -vV + cargo -V + gcc -v + g++ -v + cmake --version + curl -L https://nodejs.org/dist/v12.3.1/node-v12.3.1-linux-x64.tar.xz -o node.tar.xz + tar -xJf node.tar.xz + cp -r node-v12.3.1-linux-x64/* /usr/ + - run: + name: Install Parity + command: | + . ~/.profile + cd .. + # If the parity-ethereum directory does not exist, clone it + pwd + ls + if [ ! -d parity-ethereum ] + then + echo "Parity not installed, cloning..." + git clone https://github.com/Daohub-io/parity-ethereum.git + fi + cd parity-ethereum + git fetch --all + git checkout master + # cargo build -j 1 + # cargo build --verbose --release --features final + # strip target/debug/parity + # file target/debug/parity + if parity --version; then + echo "Parity node installed" + else + cargo install --bin parity -j 1 --path . --bin parity parity-ethereum + fi - save_cache: + key: deps7-{{ .Branch }}-cargo #-{{ checksum "kernel-ewasm/Cargo.lock" }} paths: - - evm1/node_modules - key: v1-dependencies-{{ checksum "evm1/package.json" }} - - # run tests! - - run: - name: Running Local EVM Instance - command: ./evm1/node_modules/.bin/ganache-cli + - "~/.cargo" + - "~/.rustup" + - /tmp/parity-ethereum + - checkout: + path: cap9 + - run: + name: Get Submodules + command: | + cd cap9 + git submodule update --init + - run: + name: Start local Ethereum network + command: | + . ~/.profile + cd cap9 + cd kernel-ewasm + # we need to run parity once to set up the accounts and keys + # this only needs to be active for a few seconds (hence timeout) + # timeout 5 parity --config dev || true + # We then run parity properly, now unlocking the previously setup + # account + parity --config dev --chain ./wasm-dev-chain.json --jsonrpc-apis=all --ws-apis=all --reseal-min-period 0 --gasprice 0 background: true - - run: cd evm1 && ./node_modules/.bin/truffle test - - + - run: + name: Wait for Parity startup + command: sleep 10 + - run: + name: Build Rust Component + command: | + . ~/.profile + cd cap9 + cd kernel-ewasm && ./build.sh + - run: + name: Test Rust Component + command: | + . ~/.profile + cd cap9 + cd kernel-ewasm && npm install + npm run test diff --git a/cap9-build/.gitignore b/cap9-build/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/cap9-build/.gitignore @@ -0,0 +1 @@ +target diff --git a/cap9-build/Cargo.toml b/cap9-build/Cargo.toml new file mode 100644 index 0000000..3c6b662 --- /dev/null +++ b/cap9-build/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "cap9-build" +version = "0.1.0" +authors = ["Jake O'Shannessy "] +edition = "2018" + +[dependencies] +parity-wasm = "0.31" +pwasm-utils = "0.7.0" +clap = "~2.32.0" diff --git a/cap9-build/src/main.rs b/cap9-build/src/main.rs new file mode 100644 index 0000000..affee98 --- /dev/null +++ b/cap9-build/src/main.rs @@ -0,0 +1,192 @@ +extern crate parity_wasm; +extern crate pwasm_utils; + +use parity_wasm::elements::{Module,MemoryType}; +use clap::{Arg, App, SubCommand, ArgMatches}; +use parity_wasm::elements::{Instructions, Instruction}; + +fn main() { + let matches = App::new("cap9-build") + .version("0.2.0") + .author("Cap9 ") + .about("A command-line interface for linking Cap9 procedures.") + .subcommand(SubCommand::with_name("build-proc") + .about("Convert a regular contract into a cap9 procedure.") + .arg(Arg::with_name("INPUT-FILE") + .required(true) + .help("input file")) + .arg(Arg::with_name("OUTPUT-FILE") + .required(true) + .help("output file"))) + .subcommand(SubCommand::with_name("set-mem") + .about("Set the number of memory pages in a procedure.") + .arg(Arg::with_name("INPUT-FILE") + .required(true) + .help("input file")) + .arg(Arg::with_name("OUTPUT-FILE") + .required(true) + .help("output file")) + .arg(Arg::with_name("pages") + .short("p") + .long("pages") + .value_name("PAGES") + .required(true) + .help("Number of pages to set the memory to"))) + .get_matches(); + + match matches.subcommand() { + ("build-proc", Some(opts)) => { + let input_path = opts.value_of("INPUT-FILE").expect("input file is required"); + let output_path = opts.value_of("OUTPUT-FILE").expect("output path is required"); + + let module = parity_wasm::deserialize_file(input_path).expect("parsing of input failed"); + let new_module = contract_build(module); + parity_wasm::serialize_to_file(output_path, new_module).expect("serialising to output failed"); + }, + ("set-mem", Some(opts)) => { + let input_path = opts.value_of("INPUT-FILE").expect("input file is required"); + let output_path = opts.value_of("OUTPUT-FILE").expect("output path is required"); + let mem_pages = opts.value_of("pages").expect("number of memory pages is required"); + + let module = parity_wasm::deserialize_file(input_path).expect("parsing of input failed"); + let new_module = set_mem(module, mem_pages.parse().expect("expected number for number of pages")); + parity_wasm::serialize_to_file(output_path, new_module).expect("serialising to output failed"); + }, + _ => panic!("unknown subcommand") + } +} + + +/// Perform the operations necessary for cap9 procedures. +fn contract_build(module: Module) -> Module { + + // TODO: we need to make sure these values never change between now and when + // we use them. In the current set up they will not, but it is fragile, + // there are changes that could be introduced which would change this. + let syscall_instructions_res = get_syscall_instructions(&module); + + // TODO: what is the index of this newly added function? + let mut new_module_builder = parity_wasm::builder::from_module(module); + // Add the syscall function, if applicable. + let mut new_module = if let Ok(syscall_instructions) = syscall_instructions_res { + new_module_builder + .function() + .signature() + .with_param(parity_wasm::elements::ValueType::I32) + .with_param(parity_wasm::elements::ValueType::I32) + .with_param(parity_wasm::elements::ValueType::I32) + .with_param(parity_wasm::elements::ValueType::I32) + .with_return_type(Some(parity_wasm::elements::ValueType::I32)) + .build() + .body() + .with_instructions(syscall_instructions) + .build() + .build() + .build() + } else { + new_module_builder.build() + }; + + // TODO: robustly determine the function index of the function we just + // added. I think at this point it's simply the last funciton added, thereby + // functions_space - 1, but this is not guaranteed anywhere. + let added_syscall_index = new_module.functions_space() - 1; + + // If we find cap9_syscall_low as an import, we need to replace all + // references to it with a reference to this newly added function, and + // remove the import. Once we replace the internal references and run optimize, it will be removed anyway. + let cap9_syscall_low_index = find_import(&new_module, "env", "cap9_syscall_low"); + match cap9_syscall_low_index { + None => (), + Some(syscall_index) => { + // Search though the code of each function, if we encounter a + // Call(syscall_index), replace it with Call(added_syscall_index). + // TODO: investigate the use of CallIndirect + for f in new_module.code_section_mut().unwrap().bodies_mut().iter_mut() { + for i in 0..f.code().elements().len() { + let instruction = &f.code().elements()[i]; + if instruction == &Instruction::Call(syscall_index) { + f.code_mut().elements_mut()[i] = Instruction::Call(added_syscall_index as u32); + } + } + } + } + } + + // Next we want to delete dummy_syscall if it exists. First we find it among + // the exports (if it doesn't exist we don't need to do anything). We take + // the reference of the export (i.e. the function it exports) and delete + // both that function and the export. One way to do this would be to delete + // the export and run the parity's optimizer again. + // 1. Get the index of the export + if let Some(dummy_syscall_export_index) = find_export(&new_module, "dummy_syscall") { + // println!("dummy_syscall_export_index: {}", dummy_syscall_export_index); + // 2. Delete the export + new_module.export_section_mut().unwrap().entries_mut().remove(dummy_syscall_export_index as usize); + } + // 3. At this stage the dummy_syscall function still exists internally. We + // can't use the same remove procedure without screwing up the internal + // references, so we will just run the parity optmizer again for now to + // let it deal with that. + pwasm_utils::optimize(&mut new_module, vec!["call","deploy"]).unwrap(); + new_module +} + +fn set_mem(mut module: Module, num_pages: u32) -> Module { + // We want to find the single memory section, and change it from its current + // value to the one we've requested. + let mut mem_entry: &mut Vec = module.memory_section_mut().unwrap().entries_mut(); + mem_entry[0] = parity_wasm::elements::MemoryType::new(num_pages,None); + module +} + +// Find the function index of an import +fn find_import(module: &Module, mod_name: &str, field_name: &str) -> Option { + let imports = module.import_section().unwrap().entries(); + for (i,import) in imports.iter().enumerate() { + if import.module() == mod_name && import.field() == field_name { + return Some(i as u32); + } + } + return None; +} + +// Find the function index of an export +fn find_export(module: &Module, field_name: &str) -> Option { + let exports = module.export_section().unwrap().entries(); + for (i,export) in exports.iter().enumerate() { + if export.field() == field_name { + return Some(i as u32); + } + } + return None; +} + +enum SysCallError { + NoDCall, + NoGasLeft, + NoSender, +} + +fn get_syscall_instructions(module: &Module) -> Result { + // If any of these three environments are not pulled in from the + // environment, we cannot have syscalls. + let dcall_index = find_import(module, "env", "dcall").ok_or(SysCallError::NoDCall)?; + let gasleft_index = find_import(module, "env", "gasleft").ok_or(SysCallError::NoGasLeft)?; + let sender_index = find_import(module, "env", "sender").ok_or(SysCallError::NoSender)?; + let syscall_instructions = parity_wasm::elements::Instructions::new(vec![ + // Call gas + Instruction::Call(gasleft_index), + // Call sender + Instruction::Call(sender_index), + Instruction::GetLocal(0), + Instruction::GetLocal(1), + Instruction::GetLocal(2), + Instruction::GetLocal(3), + // Do the delegate call + Instruction::Call(dcall_index), + // End function + Instruction::End, + ]); + Ok(syscall_instructions) +} diff --git a/kernel-ewasm/.gitignore b/kernel-ewasm/.gitignore index d4e2d4b..1b032ff 100644 --- a/kernel-ewasm/.gitignore +++ b/kernel-ewasm/.gitignore @@ -3,4 +3,7 @@ node_modules #CARGO target -**/*.rs.bk \ No newline at end of file +**/*.rs.bk + +#Built Contracts +build/ diff --git a/kernel-ewasm/Cargo.toml b/kernel-ewasm/Cargo.toml index 4674fa4..a62f4a6 100644 --- a/kernel-ewasm/Cargo.toml +++ b/kernel-ewasm/Cargo.toml @@ -7,11 +7,13 @@ edition = "2018" [dependencies] tiny-keccak = "1.4.2" -pwasm-std = "0.13" -pwasm-ethereum = "0.8" +pwasm-std = {version = "0.13", default-features = false} +pwasm-ethereum = {version = "0.8", default-features = false} pwasm-abi = "0.2" pwasm-abi-derive = "0.2" +parity-wasm = { git = "https://github.com/paritytech/parity-wasm.git", default-features = false } lazy_static = { version = "1.2.0", features = ["spin_no_std"] } +validator = { path = "./validator", default-features = false } [dev-dependencies.pwasm-test] git = "https://github.com/paritytech/pwasm-test" @@ -22,9 +24,11 @@ name = "kernel_ewasm" crate-type = ["cdylib"] [features] +default = ["std"] std = ["pwasm-std/std", "pwasm-ethereum/std", "pwasm-test/std"] +panic_with_msg = ["pwasm-std/panic_with_msg"] [profile.release] panic = "abort" -lto = true -opt-level = "z" +lto = false +# opt-level = "z" diff --git a/kernel-ewasm/build.bat b/kernel-ewasm/build.bat index 4667d6b..af141fd 100644 --- a/kernel-ewasm/build.bat +++ b/kernel-ewasm/build.bat @@ -1,6 +1,15 @@ +@echo off -cargo build --release --target wasm32-unknown-unknown +rustup target add wasm32-unknown-unknown +cargo install pwasm-utils-cli --bin wasm-build --version 0.6.0 +cargo install --path ..\cap9-build --bin cap9-build --force + +set contract_name=kernel_ewasm + +cargo build --release --target wasm32-unknown-unknown --no-default-features --features "panic_with_msg" +cap9-build set-mem --pages 3 .\target\wasm32-unknown-unknown\release\%contract_name%.wasm .\target\wasm32-unknown-unknown\release\%contract_name%.wasm wasm-build --target=wasm32-unknown-unknown .\target kernel-ewasm +REM ..\..\wasm-utils\target\debug\wasm-build.exe --target=wasm32-unknown-unknown .\target kernel-ewasm mkdir .\build copy .\target\*.wasm .\build diff --git a/kernel-ewasm/build.sh b/kernel-ewasm/build.sh index 83c5b14..fa12228 100755 --- a/kernel-ewasm/build.sh +++ b/kernel-ewasm/build.sh @@ -1,10 +1,15 @@ #!/bin/bash -cargo build --release --target wasm32-unknown-unknown +rustup target add wasm32-unknown-unknown +cargo install pwasm-utils-cli --bin wasm-build --version 0.6.0 +cargo install --path ../cap9-build --bin cap9-build --force + +cargo build --release --target wasm32-unknown-unknown --no-default-features --features "panic_with_msg" +cap9-build set-mem --pages 3 ./target/wasm32-unknown-unknown/release/kernel_ewasm.wasm ./target/wasm32-unknown-unknown/release/kernel_ewasm.wasm wasm-build --target=wasm32-unknown-unknown ./target kernel-ewasm rm -dr ./build mkdir -p ./build cp ./target/*.wasm ./build -cp ./target/json/* ./build \ No newline at end of file +cp ./target/json/* ./build diff --git a/kernel-ewasm/build/KernelInterface.json b/kernel-ewasm/build/KernelInterface.json index d701182..7cfa700 100644 --- a/kernel-ewasm/build/KernelInterface.json +++ b/kernel-ewasm/build/KernelInterface.json @@ -43,6 +43,60 @@ "constant": false, "payable": false }, + { + "type": "function", + "name": "check_contract", + "inputs": [ + { + "name": "_to", + "type": "address" + } + ], + "outputs": [ + { + "name": "returnValue0", + "type": "bool" + } + ], + "constant": false, + "payable": false + }, + { + "type": "function", + "name": "get_code_size", + "inputs": [ + { + "name": "_to", + "type": "address" + } + ], + "outputs": [ + { + "name": "returnValue0", + "type": "int32" + } + ], + "constant": false, + "payable": false + }, + { + "type": "function", + "name": "code_copy", + "inputs": [ + { + "name": "_to", + "type": "address" + } + ], + "outputs": [ + { + "name": "returnValue0", + "type": "bytes" + } + ], + "constant": false, + "payable": false + }, { "type": "constructor", "inputs": [ diff --git a/kernel-ewasm/build/TokenContract.json b/kernel-ewasm/build/TokenContract.json deleted file mode 100644 index 9809365..0000000 --- a/kernel-ewasm/build/TokenContract.json +++ /dev/null @@ -1,176 +0,0 @@ -[ - { - "type": "function", - "name": "balanceOf", - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "outputs": [ - { - "name": "returnValue0", - "type": "uint256" - } - ], - "constant": true, - "payable": false - }, - { - "type": "function", - "name": "totalSupply", - "inputs": [], - "outputs": [ - { - "name": "returnValue0", - "type": "uint256" - } - ], - "constant": true, - "payable": false - }, - { - "type": "function", - "name": "transfer", - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_amount", - "type": "uint256" - } - ], - "outputs": [ - { - "name": "returnValue0", - "type": "bool" - } - ], - "constant": false, - "payable": false - }, - { - "type": "function", - "name": "transferFrom", - "inputs": [ - { - "name": "_from", - "type": "address" - }, - { - "name": "_to", - "type": "address" - }, - { - "name": "_amount", - "type": "uint256" - } - ], - "outputs": [ - { - "name": "returnValue0", - "type": "bool" - } - ], - "constant": false, - "payable": false - }, - { - "type": "function", - "name": "approve", - "inputs": [ - { - "name": "_spender", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "outputs": [ - { - "name": "returnValue0", - "type": "bool" - } - ], - "constant": false, - "payable": false - }, - { - "type": "function", - "name": "allowance", - "inputs": [ - { - "name": "_owner", - "type": "address" - }, - { - "name": "_spender", - "type": "address" - } - ], - "outputs": [ - { - "name": "returnValue0", - "type": "uint256" - } - ], - "constant": false, - "payable": false - }, - { - "type": "event", - "name": "Transfer", - "inputs": [ - { - "name": "indexed_from", - "type": "address", - "indexed": true - }, - { - "name": "indexed_to", - "type": "address", - "indexed": true - }, - { - "name": "_value", - "type": "uint256", - "indexed": false - } - ] - }, - { - "type": "event", - "name": "Approval", - "inputs": [ - { - "name": "indexed_owner", - "type": "address", - "indexed": true - }, - { - "name": "indexed_spender", - "type": "address", - "indexed": true - }, - { - "name": "_value", - "type": "uint256", - "indexed": false - } - ] - }, - { - "type": "constructor", - "inputs": [ - { - "name": "_total_supply", - "type": "uint256" - } - ] - } -] \ No newline at end of file diff --git a/kernel-ewasm/build/TokenInterface.json b/kernel-ewasm/build/TokenInterface.json index 0e978fd..ce4d04c 100644 --- a/kernel-ewasm/build/TokenInterface.json +++ b/kernel-ewasm/build/TokenInterface.json @@ -74,12 +74,66 @@ ] }, { - "type": "constructor", + "type": "function", + "name": "check_contract", + "inputs": [ + { + "name": "_to", + "type": "address" + } + ], + "outputs": [ + { + "name": "returnValue0", + "type": "bool" + } + ], + "constant": false, + "payable": false + }, + { + "type": "function", + "name": "get_code_size", "inputs": [ { - "name": "_entryProcedure", + "name": "_to", "type": "address" } + ], + "outputs": [ + { + "name": "returnValue0", + "type": "int32" + } + ], + "constant": false, + "payable": false + }, + { + "type": "function", + "name": "code_copy", + "inputs": [ + { + "name": "_to", + "type": "address" + } + ], + "outputs": [ + { + "name": "returnValue0", + "type": "bytes" + } + ], + "constant": false, + "payable": false + }, + { + "type": "constructor", + "inputs": [ + { + "name": "_total_supply", + "type": "uint256" + } ] } ] \ No newline at end of file diff --git a/kernel-ewasm/build/ValidatorInterface.json b/kernel-ewasm/build/ValidatorInterface.json deleted file mode 100644 index ec73a6f..0000000 --- a/kernel-ewasm/build/ValidatorInterface.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "type": "function", - "name": "check_contract", - "inputs": [ - { - "name": "_to", - "type": "address" - } - ], - "outputs": [ - { - "name": "returnValue0", - "type": "bool" - } - ], - "constant": false, - "payable": false - } -] \ No newline at end of file diff --git a/kernel-ewasm/build/kernel-ewasm.wasm b/kernel-ewasm/build/kernel-ewasm.wasm index 32dd92d..2241238 100755 Binary files a/kernel-ewasm/build/kernel-ewasm.wasm and b/kernel-ewasm/build/kernel-ewasm.wasm differ diff --git a/kernel-ewasm/example_contract_1/.gitignore b/kernel-ewasm/example_contract_1/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/kernel-ewasm/example_contract_1/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/kernel-ewasm/example_contract_1/Cargo.toml b/kernel-ewasm/example_contract_1/Cargo.toml new file mode 100644 index 0000000..e72d369 --- /dev/null +++ b/kernel-ewasm/example_contract_1/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "example_contract_1" +version = "0.1.0" + +[dependencies] +pwasm-std = "0.13" +pwasm-ethereum = { version = "0.8", features = ["kip6"] } +pwasm-abi = "0.2" +pwasm-abi-derive = "0.2" +tiny-keccak = "1.4.2" + +[dev-dependencies] +pwasm-test = { git = "https://github.com/paritytech/pwasm-test" } +cap9-build = { path = "../../cap9-build" } +pwasm-utils-cli = "0.7.0" + +[features] +default = [] +std = ["pwasm-std/std", "pwasm-ethereum/std"] + +[lib] +crate-type = ["cdylib"] diff --git a/kernel-ewasm/example_contract_1/build.bat b/kernel-ewasm/example_contract_1/build.bat new file mode 100644 index 0000000..fe5f527 --- /dev/null +++ b/kernel-ewasm/example_contract_1/build.bat @@ -0,0 +1,17 @@ +@echo OFF + +mkdir .\build + +rustup target add wasm32-unknown-unknown +REM cargo install pwasm-utils-cli --bin wasm-build --force +cargo install --path ..\..\cap9-build --bin cap9-build --force + +set contract_name=example_contract_1 + +cargo build --release --target wasm32-unknown-unknown +REM TODO: this replaces the file in-place, but needs to be done to cooperate with wasm-build +cap9-build build-proc .\target\wasm32-unknown-unknown\release\%contract_name%.wasm .\target\wasm32-unknown-unknown\release\%contract_name%.wasm +wasm-build --target=wasm32-unknown-unknown .\target %contract_name% + +REM copy .\target\*.wasm .\build +REM copy .\target\json\* .\build diff --git a/kernel-ewasm/example_contract_1/src/lib.rs b/kernel-ewasm/example_contract_1/src/lib.rs new file mode 100644 index 0000000..32a459f --- /dev/null +++ b/kernel-ewasm/example_contract_1/src/lib.rs @@ -0,0 +1,137 @@ +#![cfg_attr(not(feature="std"), no_std)] + +#![allow(non_snake_case)] + +extern crate tiny_keccak; +extern crate pwasm_std; +extern crate pwasm_ethereum; +extern crate pwasm_abi; +extern crate pwasm_abi_derive; + +use tiny_keccak::Keccak; +use pwasm_ethereum as eth; +use pwasm_abi::types::*; +use pwasm_abi_derive::eth_abi; +use pwasm_ethereum::Error; + +/// TODO: this is duplicated from pwasm_ethereum as it is currently in a private +/// module. +extern "C" { + pub fn dcall( + gas: i64, + address: *const u8, + input_ptr: *const u8, + input_len: u32, + result_ptr: *mut u8, + result_len: u32, +) -> i32; +} + +extern "C" { + /// This extern marks an external import that we get from linking or + /// environment. Usually this would be something pulled in from the Ethereum + /// environement, but in this case we will use a later stage in the build + /// process (cap9-build) to link in our own implementation of cap9_syscall + /// to replace this import. + /// + /// A few notes on the API. All syscalls are delegate calls, therefore it + /// returns an i32 as with any other delegate call. This function here is + /// the lowest level, therefore it's arguments are all the non-compulsory + /// parts of a delgate call. That is, the signature of a delegate call is + /// this: + /// + /// dcall( gas: i64, address: *const u8, input_ptr: *const u8, input_len: + /// u32, result_ptr: *mut u8, result_len: u32, ) -> i32 + /// + /// But gas and address are fixed by the system call specification, + /// therefore we can only set the remaining parameters (input_ptr, + /// input_len, result_ptr, and result_len); + #[no_mangle] + pub fn cap9_syscall_low(input_ptr: *const u8, input_len: u32, result_ptr: *mut u8, result_len: u32, ) -> i32; +} + +/// This is to replace pwasm_ethereum::call_code, and uses cap9_syscall_low +/// underneath instead of dcall. This is a slightly higher level abstraction +/// over cap9_syscall_low that uses Result types and the like. This is by no +/// means part of the spec, but more ergonomic Rust level library code. Actual +/// syscalls should be built on top of this. +pub fn cap9_syscall(input: &[u8], result: &mut [u8]) -> Result<(), Error> { + unsafe { + if cap9_syscall_low( + input.as_ptr(), + input.len() as u32, + result.as_mut_ptr(), + result.len() as u32 + ) == 0 { + Ok(()) + } else { + Err(Error) + } + } +} + +/// This function is the rough shape of a syscall. It's only purpose is to force +/// the inclusion/import of all the necessay Ethereum functions and prevent them +/// from being deadcode eliminated. As part of this, it is also necessary to +/// pass wasm-build "dummy_syscall" as a public api parameter, to ensure that it +/// is preserved. +/// +/// TODO: this is something we would like to not have to do +#[no_mangle] +fn dummy_syscall() { + pwasm_ethereum::gas_left(); + pwasm_ethereum::sender(); + unsafe { + dcall(0,0 as *const u8, 0 as *const u8, 0, 0 as *mut u8, 0); + } +} + +/// The call function is the main function of the *deployed* contract +#[no_mangle] +pub fn call() { + // write some value + pwasm_ethereum::write(&pwasm_std::types::H256::zero(), &[0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1]); + // call another contract + pwasm_ethereum::call(0, &Address::zero(), pwasm_std::types::U256::zero(), &[], &mut [] ); + // delegate call another contract (under the hood this version of call_code + // uses delgate call). + pwasm_ethereum::gas_left(); + pwasm_ethereum::sender(); + + // An example syscall (empty input and output) + cap9_syscall(&[], &mut []); + + pwasm_ethereum::ret(&b"result"[..]); + let mut endpoint = contract::ExampleContract1Endpoint::new(contract::ExampleContract1{}); + pwasm_ethereum::ret(&endpoint.dispatch(&pwasm_ethereum::input())); +} + +// Declares the dispatch and dispatch_ctor methods +use pwasm_abi::eth::EndpointInterface; + +#[no_mangle] +pub fn deploy() { + let mut endpoint = contract::ExampleContract1Endpoint::new(contract::ExampleContract1{}); + endpoint.dispatch_ctor(&pwasm_ethereum::input()); +} + + +pub mod contract { + use super::*; + use pwasm_abi_derive::eth_abi; + + #[eth_abi(ExampleContract1Endpoint, ExampleContract1Client)] + pub trait ExampleContract1Interface { + /// Check if Procedure Contract is Valid + fn check_contract(&mut self, _to: Address) -> bool; + } + + pub struct ExampleContract1; + + impl ExampleContract1Interface for ExampleContract1 { + fn check_contract(&mut self, _target: Address) -> bool { + // unimplemented!() + false + } + } +} diff --git a/kernel-ewasm/example_contract_2/.gitignore b/kernel-ewasm/example_contract_2/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/kernel-ewasm/example_contract_2/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/kernel-ewasm/example_contract_2/Cargo.toml b/kernel-ewasm/example_contract_2/Cargo.toml new file mode 100644 index 0000000..961bd83 --- /dev/null +++ b/kernel-ewasm/example_contract_2/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "example_contract_2" +version = "0.1.0" + +[dependencies] +pwasm-std = "0.13" +pwasm-ethereum = { version = "0.8", features = ["kip6"] } +pwasm-abi = "0.2" +pwasm-abi-derive = "0.2" +tiny-keccak = "1.4.2" + +[dev-dependencies] +pwasm-test = { git = "https://github.com/paritytech/pwasm-test" } +cap9-build = { path = "../../cap9-build" } +pwasm-utils-cli = "0.7.0" + +[features] +default = [] +std = ["pwasm-std/std", "pwasm-ethereum/std"] + +[lib] +crate-type = ["cdylib"] diff --git a/kernel-ewasm/example_contract_2/build.bat b/kernel-ewasm/example_contract_2/build.bat new file mode 100644 index 0000000..c2760c5 --- /dev/null +++ b/kernel-ewasm/example_contract_2/build.bat @@ -0,0 +1,15 @@ +@echo OFF + +mkdir .\build + +rustup target add wasm32-unknown-unknown +REM cargo install pwasm-utils-cli --bin wasm-build --force + +set contract_name=example_contract_2 + +cargo build --release --target wasm32-unknown-unknown +REM We don't need to use cap9 build here as it contains no syscalls +wasm-build --target=wasm32-unknown-unknown .\target %contract_name% + +copy .\target\*.wasm .\build +copy .\target\json\* .\build diff --git a/kernel-ewasm/example_contract_2/build.sh b/kernel-ewasm/example_contract_2/build.sh new file mode 100644 index 0000000..492a95e --- /dev/null +++ b/kernel-ewasm/example_contract_2/build.sh @@ -0,0 +1,16 @@ +@echo OFF + +mkdir ./build + +rustup target add wasm32-unknown-unknown +REM cargo install pwasm-utils-cli --bin wasm-build --force +cargo install --path ../../cap9-build --bin cap9-build --force + +export contract_name=example_contract_2 + +cargo build --release --target wasm32-unknown-unknown +# We don't need to use cap9 build here as it contains no syscalls +wasm-build --target=wasm32-unknown-unknown ./target $contract_name + +cp ./target/*.wasm ./build +cp ./target/json/* ./build diff --git a/kernel-ewasm/example_contract_2/src/lib.rs b/kernel-ewasm/example_contract_2/src/lib.rs new file mode 100644 index 0000000..704b0d4 --- /dev/null +++ b/kernel-ewasm/example_contract_2/src/lib.rs @@ -0,0 +1,52 @@ +#![cfg_attr(not(feature="std"), no_std)] + +#![allow(non_snake_case)] + +extern crate tiny_keccak; +extern crate pwasm_std; +extern crate pwasm_ethereum; +extern crate pwasm_abi; +extern crate pwasm_abi_derive; + +use tiny_keccak::Keccak; +use pwasm_ethereum as eth; +use pwasm_abi::types::*; +use pwasm_abi_derive::eth_abi; +use pwasm_ethereum::Error; + +/// The call function is the main function of the *deployed* contract +#[no_mangle] +pub fn call() { + let mut endpoint = contract::ExampleContract2Endpoint::new(contract::ExampleContract2{}); + pwasm_ethereum::ret(&endpoint.dispatch(&pwasm_ethereum::input())); +} + +// Declares the dispatch and dispatch_ctor methods +use pwasm_abi::eth::EndpointInterface; + +#[no_mangle] +pub fn deploy() { + let mut endpoint = contract::ExampleContract2Endpoint::new(contract::ExampleContract2{}); + endpoint.dispatch_ctor(&pwasm_ethereum::input()); +} + + +pub mod contract { + use super::*; + use pwasm_abi_derive::eth_abi; + + #[eth_abi(ExampleContract2Endpoint, ExampleContract2Client)] + pub trait ExampleContract2Interface { + /// Check if Procedure Contract is Valid + fn check_contract(&mut self, _to: Address) -> bool; + } + + pub struct ExampleContract2; + + impl ExampleContract2Interface for ExampleContract2 { + fn check_contract(&mut self, _target: Address) -> bool { + // unimplemented!() + false + } + } +} diff --git a/kernel-ewasm/package.json b/kernel-ewasm/package.json index 2166094..151f515 100644 --- a/kernel-ewasm/package.json +++ b/kernel-ewasm/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "deploy": "node script/deploy.js", - "test": "./build.sh && mocha tests/**/**.js" + "test": "sh ./test.sh" }, "author": "", "license": "ISC", diff --git a/kernel-ewasm/rust-toolchain b/kernel-ewasm/rust-toolchain index 110fe36..09c2799 100644 --- a/kernel-ewasm/rust-toolchain +++ b/kernel-ewasm/rust-toolchain @@ -1 +1 @@ -nightly-2019-03-05 \ No newline at end of file +nightly-2019-06-06 diff --git a/kernel-ewasm/src/lib.rs b/kernel-ewasm/src/lib.rs index e1b7b27..2717cdb 100644 --- a/kernel-ewasm/src/lib.rs +++ b/kernel-ewasm/src/lib.rs @@ -4,16 +4,44 @@ extern crate pwasm_abi; extern crate pwasm_abi_derive; -extern crate pwasm_ethereum; -extern crate pwasm_std; +extern crate parity_wasm; +extern crate validator; +use pwasm_abi::types::*; +use core::default::Default; pub mod proc_table; -pub mod validator; + +pub mod ext { + extern "C" { + pub fn extcodesize( address: *const u8) -> i32; + pub fn extcodecopy( dest: *mut u8, address: *const u8); + } +} + +pub fn extcodesize(address: &Address) -> i32 { + unsafe { ext::extcodesize(address.as_ptr()) } +} + +pub fn extcodecopy(address: &Address) -> pwasm_std::Vec { + let len = unsafe { ext::extcodesize(address.as_ptr()) }; + match len { + 0 => pwasm_std::Vec::new(), + non_zero => { + let mut data = pwasm_std::Vec::with_capacity(non_zero as usize); + unsafe { + data.set_len(non_zero as usize); + ext::extcodecopy(data.as_mut_ptr(), address.as_ptr()); + } + data + } + } +} type ProcedureKey = [u8; 24]; pub mod token { use pwasm_abi::types::*; + use validator::{Validity, Module}; use pwasm_ethereum; // eth_abi is a procedural macros https://doc.rust-lang.org/book/first-edition/procedural-macros.html @@ -44,6 +72,12 @@ pub mod token { /// Get Procedure Address By Key /// Returns 0 if Procedure Not Found fn getProcedureByKey(&mut self, _proc_key: String) -> Address; + /// Check if Procedure Contract is Valid + fn check_contract(&mut self, _to: Address) -> bool; + /// Get the size (in bytes) of another contract + fn get_code_size(&mut self, _to: Address) -> i32; + /// Copy the code of another contract into memory + fn code_copy(&mut self, _to: Address) -> pwasm_std::Vec; } pub struct KernelContract; @@ -56,7 +90,7 @@ pub mod token { // pwasm_ethereum::write(&balance_key(&sender), &total_supply.into()); // // Set the contract owner // pwasm_ethereum::write(&OWNER_KEY, &H256::from(sender).into()); - unimplemented!() + // unimplemented!() } fn entryProcedure(&mut self) -> String { @@ -70,6 +104,27 @@ pub mod token { fn getProcedureByKey(&mut self, _proc_key: String) -> Address { unimplemented!() } + + fn check_contract(&mut self, target: Address) -> bool { + // First we check if the target is the null address. If so we return + // false. + if target == H160::zero() { + false + } else { + // Next we get the code of the contract, using EXTCODECOPY under + // the hood. + let code: pwasm_std::Vec = self.code_copy(target); + Module::new(code.as_slice()).is_valid() + } + } + + fn get_code_size(&mut self, to: Address) -> i32 { + super::extcodesize(&to) + } + + fn code_copy(&mut self, to: Address) -> pwasm_std::Vec { + super::extcodecopy(&to) + } } } // Declares the dispatch and dispatch_ctor methods diff --git a/kernel-ewasm/src/proc_table/mod.rs b/kernel-ewasm/src/proc_table/mod.rs index ae6424d..73472a0 100644 --- a/kernel-ewasm/src/proc_table/mod.rs +++ b/kernel-ewasm/src/proc_table/mod.rs @@ -91,7 +91,7 @@ impl ProcPointer { result[5..29].copy_from_slice(&slice[8..]); result } - + } /// Error or Procedure Insertion @@ -825,7 +825,7 @@ mod tests { ); Capability::from_u256_list(&raw_cap).expect("Should be Valid LogCap") }; - + assert_eq!(new_write_cap, sample_write_cap.cap); assert_eq!(new_log_cap, sample_log_cap.cap); } @@ -858,7 +858,7 @@ mod tests { let cap_list = NewCapList([].to_vec()).to_u256_list(); contract.insert_proc(String::from("FOO"), proc_address, cap_list); - + let err = contract.set_current_proc_id(String::from("FOO")); if err != U256::zero() { panic!("Should return 0 for success, instead got {}", err) @@ -866,7 +866,7 @@ mod tests { let mut new_current_proc_id = contract.get_current_proc_id(); new_current_proc_id.truncate(3); - + assert_eq!(new_current_proc_id, String::from("FOO")); } } diff --git a/kernel-ewasm/src/validator.rs b/kernel-ewasm/src/validator.rs index 5da878f..9225ece 100644 --- a/kernel-ewasm/src/validator.rs +++ b/kernel-ewasm/src/validator.rs @@ -1,11 +1,13 @@ -extern crate pwasm_abi; -extern crate pwasm_abi_derive; -extern crate pwasm_ethereum; -extern crate pwasm_std; +use pwasm_abi; +use pwasm_ethereum; +use pwasm_std; +use pwasm_abi_derive; use pwasm_abi::types::*; use pwasm_abi_derive::eth_abi; +// use validator::*; + pub fn check_contract(bytecode: &[u8]) -> bool { false } diff --git a/kernel-ewasm/test.sh b/kernel-ewasm/test.sh new file mode 100644 index 0000000..527c297 --- /dev/null +++ b/kernel-ewasm/test.sh @@ -0,0 +1,4 @@ +pushd example_contract_2 +sh ./build.sh +popd +mocha tests/**/**.js diff --git a/kernel-ewasm/tests/integration/index.js b/kernel-ewasm/tests/integration/index.js index bde2c3c..c74986e 100644 --- a/kernel-ewasm/tests/integration/index.js +++ b/kernel-ewasm/tests/integration/index.js @@ -107,10 +107,41 @@ async function newKernelInstance(proc_key, proc_address) { return contract; } +async function deployContract(interfacePath, codePath) { + try { + + // Create Account + const newAccount = await createAccount(DEFAULT_ACCOUNT.NAME, DEFAULT_ACCOUNT.PASSWORD); + const accounts = await web3.eth.personal.getAccounts() + if (accounts.length == 0) throw `Got zero accounts`; + const account = web3.utils.toChecksumAddress(accounts[0], web3.utils.hexToNumber(CHAIN_CONFIG.params.networkId)); + web3.eth.defaultAccount = account; + // read JSON ABI + const abi = JSON.parse(fs.readFileSync(path.resolve(interfacePath))); + // convert Wasm binary to hex format + const codeHex = '0x' + fs.readFileSync(path.resolve(codePath)).toString('hex'); + const Contract = new web3.eth.Contract(abi, null, { data: codeHex, from: account, transactionConfirmationBlocks: 1 }); + const DeployTransaction = Contract.deploy({ data: codeHex, arguments: [] }); + await web3.eth.personal.unlockAccount(accounts[0], "user", null) + let gas = await DeployTransaction.estimateGas() + let contract_tx = DeployTransaction.send({ gasLimit: gas, from: account }) + let tx_hash = await new Promise((res, rej) => contract_tx.on('transactionHash', res).on('error', rej)); + let tx_receipt = await web3.eth.getTransactionReceipt(tx_hash); + let contract_addr = tx_receipt.contractAddress; + let contract = Contract.clone(); + contract.address = contract_addr; + return contract; + } catch (e) { + throw new Error(e); + } +} + describe('Kernel', function () { - describe('constructor', function () { - it('should have correct Initial Entry Procedure', async function () { + describe('constructor', function() { + this.timeout(20000); + // Currently skipped because it's unimplemented. + it.skip('should have correct Initial Entry Procedure', async function () { let contract = await newKernelInstance("init", "0xc1912fee45d61c87cc5ea59dae31190fffff232d"); // Check entryProcedure @@ -122,4 +153,72 @@ describe('Kernel', function () { assert.strictEqual(currentProcedureKey, "") }) }) + + describe('validator', function() { + this.timeout(20000); + let kernel; + + before(async function () { + kernel = await newKernelInstance("init", "0xc1912fee45d61c87cc5ea59dae31190fffff232d"); + + }) + + it('should return false when given the null address', async function() { + this.timeout(20000); + let rec_validation = await kernel.methods.check_contract('0x0000000000000000000000000000000000000000').call(); + assert.strictEqual(rec_validation, false) + }) + it('should return panic when given an account addeess (as there is no code)', async function() { + const accounts = await web3.eth.personal.getAccounts() + assert(web3.utils.isAddress(accounts[0]), "The example should be a valid address") + try { + let rec_validation = await kernel.methods.check_contract(accounts[0]).call(); + throw new Error("check_contract should no succeed"); + } catch (e) { + // console.log(e) + } + }) + it('should return the code size of the kernel', async function() { + const kernelAddress = kernel.options.address; + assert(web3.utils.isAddress(kernelAddress), "The kernel address should be a valid address") + let rec_validation = await kernel.methods.get_code_size(kernelAddress).call(); + assert.strictEqual(typeof rec_validation, "number") + }) + + it('should copy the code of the kernel', async function() { + const kernelAddress = kernel.options.address; + assert(web3.utils.isAddress(kernelAddress), "The kernel address should be a valid address") + const code_size = await kernel.methods.get_code_size(kernelAddress).call(); + const code_hex = await kernel.methods.code_copy(kernelAddress).call(); + const code = web3.utils.hexToBytes(code_hex); + assert.strictEqual(code_size, code.length, "The code length should be as given by EXTCODESIZE"); + }) + + it('should return a boolean when trying to validate the kernel itself', async function() { + const kernelAddress = kernel.options.address; + assert(web3.utils.isAddress(kernelAddress), "The kernel address should be a valid address") + let rec_validation = await kernel.methods.check_contract(kernelAddress).call(); + assert.strictEqual(typeof rec_validation, "boolean"); + }) + + it('should copy the code of an example contract', async function() { + const contract = await deployContract("example_contract_2/build/ExampleContract2Interface.json", "example_contract_2/build/example_contract_2.wasm"); + assert(web3.utils.isAddress(contract.address), "The contract address should be a valid address") + // const code_size = await kernel.methods.get_code_size(contract.address).call(); + // const code_hex = await kernel.methods.code_copy(contract.address).call(); + // const code = web3.utils.hexToBytes(code_hex); + // assert.strictEqual(code_size, code.length, "The code length should be as given by EXTCODESIZE"); + }) + + it('should return a boolean when validating an example contract', async function() { + const contract = await deployContract("example_contract_2/build/ExampleContract2Interface.json", "example_contract_2/build/example_contract_2.wasm"); + assert(web3.utils.isAddress(contract.address), "The contract address should be a valid address") + const code_size = await kernel.methods.get_code_size(contract.address).call(); + const code_hex = await kernel.methods.code_copy(contract.address).call(); + const code = web3.utils.hexToBytes(code_hex); + assert.strictEqual(code_size, code.length, "The code length should be as given by EXTCODESIZE"); + let rec_validation = await kernel.methods.check_contract(contract.address).call(); + assert.strictEqual(typeof rec_validation, "boolean"); + }) + }) }) diff --git a/kernel-ewasm/validator/Cargo.toml b/kernel-ewasm/validator/Cargo.toml new file mode 100644 index 0000000..7ff6d68 --- /dev/null +++ b/kernel-ewasm/validator/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "validator" +version = "0.1.0" +authors = ["Daohub Inc "] +edition = "2018" + +[dependencies] +pwasm-std = {version = "0.13", default-features = false} +pwasm-ethereum = {version = "0.8", default-features = false} + +[dev-dependencies] +wabt = "0.7.1" + +[features] +default = ["std"] +std = ["pwasm-std/std", "pwasm-ethereum/std"] + +[lib] +name = "validator" +# crate-type = ["cdylib"] diff --git a/kernel-ewasm/validator/src/func.rs b/kernel-ewasm/validator/src/func.rs new file mode 100644 index 0000000..48bc964 --- /dev/null +++ b/kernel-ewasm/validator/src/func.rs @@ -0,0 +1,118 @@ +use crate::io; +// mod primitives; +use crate::{Deserialize, VarUint32}; +use crate::types::ValueType; +use crate::instructions::{Instructions}; +use crate::serialization::{Error}; +use pwasm_std::vec::Vec; + +/// Function signature (type reference) +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Func(u32); + +impl Func { + /// New function signature + pub fn new(type_ref: u32) -> Self { Func(type_ref) } + + /// Function signature type reference. + pub fn type_ref(&self) -> u32 { + self.0 + } + + /// Function signature type reference (mutable). + pub fn type_ref_mut(&mut self) -> &mut u32 { + &mut self.0 + } +} + +// impl Deserialize for Func { +// type Error = Error; + +// fn deserialize(reader: &mut R) -> Result { +// Ok(Func(VarUint32::deserialize(reader)?.into())) +// } +// } + +/// Local definition inside the function body. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Local { + count: u32, + value_type: ValueType, +} + +impl Local { + /// New local with `count` and `value_type`. + pub fn new(count: u32, value_type: ValueType) -> Self { + Local { count: count, value_type: value_type } + } + + /// Number of locals with the shared type. + pub fn count(&self) -> u32 { self.count } + + /// Type of the locals. + pub fn value_type(&self) -> ValueType { self.value_type } +} + +impl Deserialize for Local { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let count = VarUint32::deserialize(reader)?; + let value_type = ValueType::deserialize(reader)?; + Ok(Local { count: count.into(), value_type: value_type }) + } +} + +/// Function body definition. +#[derive(Debug, Clone, PartialEq)] +pub struct FuncBody { + locals: Vec, + instructions: Instructions, +} + +impl FuncBody { + /// New function body with given `locals` and `instructions`. + pub fn new(locals: Vec, instructions: Instructions) -> Self { + FuncBody { locals: locals, instructions: instructions } + } + + /// List of individual instructions. + pub fn empty() -> Self { + FuncBody { locals: Vec::new(), instructions: Instructions::empty() } + } + + /// Locals declared in function body. + pub fn locals(&self) -> &[Local] { &self.locals } + + /// Instruction list of the function body. Minimal instruction list + /// + /// is just `&[Instruction::End]` + pub fn code(&self) -> &Instructions { &self.instructions } + + /// Locals declared in function body (mutable). + pub fn locals_mut(&mut self) -> &mut Vec { &mut self.locals } + + /// Instruction list of the function body (mutable). + pub fn code_mut(&mut self) -> &mut Instructions { &mut self.instructions } +} + +// impl Deserialize for FuncBody { +// type Error = Error; + +// fn deserialize(reader: &mut R) -> Result { +// // Why do we need to use a section reader here? +// let mut body_reader = SectionReader::new(reader)?; +// let locals: Vec = CountedList::::deserialize(&mut body_reader)?.into_inner(); + +// // The specification obliges us to count the total number of local variables while +// // decoding the binary format. +// locals +// .iter() +// .try_fold(0u32, |acc, &Local { count, .. }| acc.checked_add(count)) +// .ok_or_else(|| Error::TooManyLocals)?; + +// let instructions = Instructions::deserialize(&mut body_reader)?; +// body_reader.close()?; +// Ok(FuncBody { locals: locals, instructions: instructions }) +// } +// } diff --git a/kernel-ewasm/validator/src/import_entry.rs b/kernel-ewasm/validator/src/import_entry.rs new file mode 100644 index 0000000..a6c1ed4 --- /dev/null +++ b/kernel-ewasm/validator/src/import_entry.rs @@ -0,0 +1,245 @@ +use pwasm_std; +use pwasm_std::String; + +use crate::io; +use crate::{Deserialize, Uint8, VarUint32, VarUint1, VarUint7}; +use crate::types::{TableElementType, ValueType}; +use crate::serialization::{Error}; + +const FLAG_HAS_MAX: u8 = 0x01; +const FLAG_SHARED: u8 = 0x02; + +/// Global definition struct +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct GlobalType { + content_type: ValueType, + is_mutable: bool, +} + +impl GlobalType { + /// New global type + pub fn new(content_type: ValueType, is_mutable: bool) -> Self { + GlobalType { + content_type: content_type, + is_mutable: is_mutable, + } + } + + /// Type of the global entry + pub fn content_type(&self) -> ValueType { self.content_type } + + /// Is global entry is declared as mutable + pub fn is_mutable(&self) -> bool { self.is_mutable } +} + +impl Deserialize for GlobalType { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let content_type = ValueType::deserialize(reader)?; + let is_mutable = VarUint1::deserialize(reader)?; + Ok(GlobalType { + content_type: content_type, + is_mutable: is_mutable.into(), + }) + } +} + +/// Table entry +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct TableType { + elem_type: TableElementType, + limits: ResizableLimits, +} + +impl TableType { + /// New table definition + pub fn new(min: u32, max: Option) -> Self { + TableType { + elem_type: TableElementType::AnyFunc, + limits: ResizableLimits::new(min, max), + } + } + + /// Table memory specification + pub fn limits(&self) -> &ResizableLimits { &self.limits } + + /// Table element type + pub fn elem_type(&self) -> TableElementType { self.elem_type } +} + +impl Deserialize for TableType { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let elem_type = TableElementType::deserialize(reader)?; + let limits = ResizableLimits::deserialize(reader)?; + Ok(TableType { + elem_type: elem_type, + limits: limits, + }) + } +} + +/// Memory and table limits. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct ResizableLimits { + initial: u32, + maximum: Option, + shared: bool, +} + +impl ResizableLimits { + /// New memory limits definition. + pub fn new(min: u32, max: Option) -> Self { + ResizableLimits { + initial: min, + maximum: max, + shared: false, + } + } + /// Initial size. + pub fn initial(&self) -> u32 { self.initial } + /// Maximum size. + pub fn maximum(&self) -> Option { self.maximum } + /// Whether or not this is a shared array buffer. + pub fn shared(&self) -> bool { self.shared } +} + +impl Deserialize for ResizableLimits { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let flags: u8 = Uint8::deserialize(reader)?.into(); + match flags { + 0x00 | 0x01 | 0x03 => {}, + _ => return Err(Error::InvalidLimitsFlags(flags)), + } + + let initial = VarUint32::deserialize(reader)?; + let maximum = if flags & FLAG_HAS_MAX != 0 { + Some(VarUint32::deserialize(reader)?.into()) + } else { + None + }; + let shared = flags & FLAG_SHARED != 0; + + Ok(ResizableLimits { + initial: initial.into(), + maximum: maximum, + shared, + }) + } +} + +/// Memory entry. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct MemoryType(ResizableLimits); + +impl MemoryType { + /// New memory definition + pub fn new(min: u32, max: Option, shared: bool) -> Self { + let mut r = ResizableLimits::new(min, max); + r.shared = shared; + MemoryType(r) + } + + /// Limits of the memory entry. + pub fn limits(&self) -> &ResizableLimits { + &self.0 + } +} + +impl Deserialize for MemoryType { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + Ok(MemoryType(ResizableLimits::deserialize(reader)?)) + } +} + +/// External to local binding. +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum External { + /// Binds to a function whose type is associated with the given index in the + /// type section. + Function(u32), + /// Describes local table definition to be imported as. + Table(TableType), + /// Describes local memory definition to be imported as. + Memory(MemoryType), + /// Describes local global entry to be imported as. + Global(GlobalType), +} + +impl Deserialize for External { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let kind = VarUint7::deserialize(reader)?; + match kind.into() { + 0x00 => Ok(External::Function(VarUint32::deserialize(reader)?.into())), + 0x01 => Ok(External::Table(TableType::deserialize(reader)?)), + 0x02 => Ok(External::Memory(MemoryType::deserialize(reader)?)), + 0x03 => Ok(External::Global(GlobalType::deserialize(reader)?)), + _ => Err(Error::UnknownExternalKind(kind.into())), + } + } +} + +/// Import entry. +#[derive(Debug, Clone, PartialEq)] +pub struct ImportEntry { + module_str: String, + field_str: String, + external: External, +} + +impl ImportEntry { + /// New import entry. + pub fn new(module_str: String, field_str: String, external: External) -> Self { + ImportEntry { + module_str: module_str, + field_str: field_str, + external: external, + } + } + + /// Module reference of the import entry. + pub fn module(&self) -> &str { &self.module_str } + + /// Module reference of the import entry (mutable). + pub fn module_mut(&mut self) -> &mut String { + &mut self.module_str + } + + /// Field reference of the import entry. + pub fn field(&self) -> &str { &self.field_str } + + /// Field reference of the import entry (mutable) + pub fn field_mut(&mut self) -> &mut String { + &mut self.field_str + } + + /// Local binidng of the import entry. + pub fn external(&self) -> &External { &self.external } + + /// Local binidng of the import entry (mutable) + pub fn external_mut(&mut self) -> &mut External { &mut self.external } +} + +impl Deserialize for ImportEntry { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let module_str = String::deserialize(reader)?; + let field_str = String::deserialize(reader)?; + let external = External::deserialize(reader)?; + + Ok(ImportEntry { + module_str: module_str, + field_str: field_str, + external: external, + }) + } +} diff --git a/kernel-ewasm/validator/src/instructions.rs b/kernel-ewasm/validator/src/instructions.rs new file mode 100644 index 0000000..d98c21a --- /dev/null +++ b/kernel-ewasm/validator/src/instructions.rs @@ -0,0 +1,1626 @@ +// This file is based on parity-wasm from parity, MIT & Apache Licensed +use pwasm_std; +use pwasm_std::vec::Vec; +use pwasm_std::Box; + +use crate::io; +use crate::{Deserialize, + Uint8, VarUint32, CountedList, + Uint32, Uint64, + VarInt32, VarInt64, +}; +use crate::types::{BlockType}; +use crate::serialization::{Error}; + +/// List of instructions (usually inside a block section). +#[derive(Debug, Clone, PartialEq)] +pub struct Instructions(Vec); + +impl Instructions { + /// New list of instructions from vector of instructions. + pub fn new(elements: Vec) -> Self { + Instructions(elements) + } + + /// Empty expression with only `Instruction::End` instruction. + pub fn empty() -> Self { + Instructions(vec![Instruction::End]) + } + + /// List of individual instructions. + pub fn elements(&self) -> &[Instruction] { &self.0 } + + /// Individual instructions, mutable. + pub fn elements_mut(&mut self) -> &mut Vec { &mut self.0 } +} + +impl Deserialize for Instructions { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let mut instructions = Vec::new(); + let mut block_count = 1usize; + + loop { + let instruction = Instruction::deserialize(reader)?; + if instruction.is_terminal() { + block_count -= 1; + } else if instruction.is_block() { + block_count = block_count.checked_add(1).ok_or(Error::Other("too many instructions"))?; + } + + instructions.push(instruction); + if block_count == 0 { + break; + } + } + + Ok(Instructions(instructions)) + } +} + +/// Initialization expression. +#[derive(Debug, Clone, PartialEq)] +pub struct InitExpr(Vec); + +impl InitExpr { + /// New initialization expression from instruction list. + /// + /// `code` must end with the `Instruction::End` instruction! + pub fn new(code: Vec) -> Self { + InitExpr(code) + } + + /// Empty expression with only `Instruction::End` instruction. + pub fn empty() -> Self { + InitExpr(vec![Instruction::End]) + } + + /// List of instructions used in the expression. + pub fn code(&self) -> &[Instruction] { + &self.0 + } + + /// List of instructions used in the expression. + pub fn code_mut(&mut self) -> &mut Vec { + &mut self.0 + } +} + +impl Deserialize for InitExpr { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let mut instructions = Vec::new(); + + loop { + let instruction = Instruction::deserialize(reader)?; + let is_terminal = instruction.is_terminal(); + instructions.push(instruction); + if is_terminal { + break; + } + } + + Ok(InitExpr(instructions)) + } +} + +/// Instruction. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[allow(missing_docs)] +pub enum Instruction { + Unreachable, + Nop, + Block(BlockType), + Loop(BlockType), + If(BlockType), + Else, + End, + Br(u32), + BrIf(u32), + BrTable(Box), + Return, + + Call(u32), + CallIndirect(u32, u8), + + Drop, + Select, + + GetLocal(u32), + SetLocal(u32), + TeeLocal(u32), + GetGlobal(u32), + SetGlobal(u32), + + // All store/load instructions operate with 'memory immediates' + // which represented here as (flag, offset) tuple + I32Load(u32, u32), + I64Load(u32, u32), + F32Load(u32, u32), + F64Load(u32, u32), + I32Load8S(u32, u32), + I32Load8U(u32, u32), + I32Load16S(u32, u32), + I32Load16U(u32, u32), + I64Load8S(u32, u32), + I64Load8U(u32, u32), + I64Load16S(u32, u32), + I64Load16U(u32, u32), + I64Load32S(u32, u32), + I64Load32U(u32, u32), + I32Store(u32, u32), + I64Store(u32, u32), + F32Store(u32, u32), + F64Store(u32, u32), + I32Store8(u32, u32), + I32Store16(u32, u32), + I64Store8(u32, u32), + I64Store16(u32, u32), + I64Store32(u32, u32), + + CurrentMemory(u8), + GrowMemory(u8), + + I32Const(i32), + I64Const(i64), + F32Const(u32), + F64Const(u64), + + I32Eqz, + I32Eq, + I32Ne, + I32LtS, + I32LtU, + I32GtS, + I32GtU, + I32LeS, + I32LeU, + I32GeS, + I32GeU, + + I64Eqz, + I64Eq, + I64Ne, + I64LtS, + I64LtU, + I64GtS, + I64GtU, + I64LeS, + I64LeU, + I64GeS, + I64GeU, + + F32Eq, + F32Ne, + F32Lt, + F32Gt, + F32Le, + F32Ge, + + F64Eq, + F64Ne, + F64Lt, + F64Gt, + F64Le, + F64Ge, + + I32Clz, + I32Ctz, + I32Popcnt, + I32Add, + I32Sub, + I32Mul, + I32DivS, + I32DivU, + I32RemS, + I32RemU, + I32And, + I32Or, + I32Xor, + I32Shl, + I32ShrS, + I32ShrU, + I32Rotl, + I32Rotr, + + I64Clz, + I64Ctz, + I64Popcnt, + I64Add, + I64Sub, + I64Mul, + I64DivS, + I64DivU, + I64RemS, + I64RemU, + I64And, + I64Or, + I64Xor, + I64Shl, + I64ShrS, + I64ShrU, + I64Rotl, + I64Rotr, + F32Abs, + F32Neg, + F32Ceil, + F32Floor, + F32Trunc, + F32Nearest, + F32Sqrt, + F32Add, + F32Sub, + F32Mul, + F32Div, + F32Min, + F32Max, + F32Copysign, + F64Abs, + F64Neg, + F64Ceil, + F64Floor, + F64Trunc, + F64Nearest, + F64Sqrt, + F64Add, + F64Sub, + F64Mul, + F64Div, + F64Min, + F64Max, + F64Copysign, + + I32WrapI64, + I32TruncSF32, + I32TruncUF32, + I32TruncSF64, + I32TruncUF64, + I64ExtendSI32, + I64ExtendUI32, + I64TruncSF32, + I64TruncUF32, + I64TruncSF64, + I64TruncUF64, + F32ConvertSI32, + F32ConvertUI32, + F32ConvertSI64, + F32ConvertUI64, + F32DemoteF64, + F64ConvertSI32, + F64ConvertUI32, + F64ConvertSI64, + F64ConvertUI64, + F64PromoteF32, + + I32ReinterpretF32, + I64ReinterpretF64, + F32ReinterpretI32, + F64ReinterpretI64, + + I32Extend8S, + I32Extend16S, + I64Extend8S, + I64Extend16S, + I64Extend32S, + + AtomicWake(MemArg), + I32AtomicWait(MemArg), + I64AtomicWait(MemArg), + + I32AtomicLoad(MemArg), + I64AtomicLoad(MemArg), + I32AtomicLoad8u(MemArg), + I32AtomicLoad16u(MemArg), + I64AtomicLoad8u(MemArg), + I64AtomicLoad16u(MemArg), + I64AtomicLoad32u(MemArg), + I32AtomicStore(MemArg), + I64AtomicStore(MemArg), + I32AtomicStore8u(MemArg), + I32AtomicStore16u(MemArg), + I64AtomicStore8u(MemArg), + I64AtomicStore16u(MemArg), + I64AtomicStore32u(MemArg), + + I32AtomicRmwAdd(MemArg), + I64AtomicRmwAdd(MemArg), + I32AtomicRmwAdd8u(MemArg), + I32AtomicRmwAdd16u(MemArg), + I64AtomicRmwAdd8u(MemArg), + I64AtomicRmwAdd16u(MemArg), + I64AtomicRmwAdd32u(MemArg), + + I32AtomicRmwSub(MemArg), + I64AtomicRmwSub(MemArg), + I32AtomicRmwSub8u(MemArg), + I32AtomicRmwSub16u(MemArg), + I64AtomicRmwSub8u(MemArg), + I64AtomicRmwSub16u(MemArg), + I64AtomicRmwSub32u(MemArg), + + I32AtomicRmwAnd(MemArg), + I64AtomicRmwAnd(MemArg), + I32AtomicRmwAnd8u(MemArg), + I32AtomicRmwAnd16u(MemArg), + I64AtomicRmwAnd8u(MemArg), + I64AtomicRmwAnd16u(MemArg), + I64AtomicRmwAnd32u(MemArg), + + I32AtomicRmwOr(MemArg), + I64AtomicRmwOr(MemArg), + I32AtomicRmwOr8u(MemArg), + I32AtomicRmwOr16u(MemArg), + I64AtomicRmwOr8u(MemArg), + I64AtomicRmwOr16u(MemArg), + I64AtomicRmwOr32u(MemArg), + + I32AtomicRmwXor(MemArg), + I64AtomicRmwXor(MemArg), + I32AtomicRmwXor8u(MemArg), + I32AtomicRmwXor16u(MemArg), + I64AtomicRmwXor8u(MemArg), + I64AtomicRmwXor16u(MemArg), + I64AtomicRmwXor32u(MemArg), + + I32AtomicRmwXchg(MemArg), + I64AtomicRmwXchg(MemArg), + I32AtomicRmwXchg8u(MemArg), + I32AtomicRmwXchg16u(MemArg), + I64AtomicRmwXchg8u(MemArg), + I64AtomicRmwXchg16u(MemArg), + I64AtomicRmwXchg32u(MemArg), + + I32AtomicRmwCmpxchg(MemArg), + I64AtomicRmwCmpxchg(MemArg), + I32AtomicRmwCmpxchg8u(MemArg), + I32AtomicRmwCmpxchg16u(MemArg), + I64AtomicRmwCmpxchg8u(MemArg), + I64AtomicRmwCmpxchg16u(MemArg), + I64AtomicRmwCmpxchg32u(MemArg), + + V128Const(Box<[u8; 16]>), + V128Load(MemArg), + V128Store(MemArg), + I8x16Splat, + I16x8Splat, + I32x4Splat, + I64x2Splat, + F32x4Splat, + F64x2Splat, + I8x16ExtractLaneS(u8), + I8x16ExtractLaneU(u8), + I16x8ExtractLaneS(u8), + I16x8ExtractLaneU(u8), + I32x4ExtractLane(u8), + I64x2ExtractLane(u8), + F32x4ExtractLane(u8), + F64x2ExtractLane(u8), + I8x16ReplaceLane(u8), + I16x8ReplaceLane(u8), + I32x4ReplaceLane(u8), + I64x2ReplaceLane(u8), + F32x4ReplaceLane(u8), + F64x2ReplaceLane(u8), + V8x16Shuffle(Box<[u8; 16]>), + I8x16Add, + I16x8Add, + I32x4Add, + I64x2Add, + I8x16Sub, + I16x8Sub, + I32x4Sub, + I64x2Sub, + I8x16Mul, + I16x8Mul, + I32x4Mul, + // I64x2Mul, + I8x16Neg, + I16x8Neg, + I32x4Neg, + I64x2Neg, + I8x16AddSaturateS, + I8x16AddSaturateU, + I16x8AddSaturateS, + I16x8AddSaturateU, + I8x16SubSaturateS, + I8x16SubSaturateU, + I16x8SubSaturateS, + I16x8SubSaturateU, + I8x16Shl, + I16x8Shl, + I32x4Shl, + I64x2Shl, + I8x16ShrS, + I8x16ShrU, + I16x8ShrS, + I16x8ShrU, + I32x4ShrS, + I32x4ShrU, + I64x2ShrS, + I64x2ShrU, + V128And, + V128Or, + V128Xor, + V128Not, + V128Bitselect, + I8x16AnyTrue, + I16x8AnyTrue, + I32x4AnyTrue, + I64x2AnyTrue, + I8x16AllTrue, + I16x8AllTrue, + I32x4AllTrue, + I64x2AllTrue, + I8x16Eq, + I16x8Eq, + I32x4Eq, + // I64x2Eq, + F32x4Eq, + F64x2Eq, + I8x16Ne, + I16x8Ne, + I32x4Ne, + // I64x2Ne, + F32x4Ne, + F64x2Ne, + I8x16LtS, + I8x16LtU, + I16x8LtS, + I16x8LtU, + I32x4LtS, + I32x4LtU, + // I64x2LtS, + // I64x2LtU, + F32x4Lt, + F64x2Lt, + I8x16LeS, + I8x16LeU, + I16x8LeS, + I16x8LeU, + I32x4LeS, + I32x4LeU, + // I64x2LeS, + // I64x2LeU, + F32x4Le, + F64x2Le, + I8x16GtS, + I8x16GtU, + I16x8GtS, + I16x8GtU, + I32x4GtS, + I32x4GtU, + // I64x2GtS, + // I64x2GtU, + F32x4Gt, + F64x2Gt, + I8x16GeS, + I8x16GeU, + I16x8GeS, + I16x8GeU, + I32x4GeS, + I32x4GeU, + // I64x2GeS, + // I64x2GeU, + F32x4Ge, + F64x2Ge, + F32x4Neg, + F64x2Neg, + F32x4Abs, + F64x2Abs, + F32x4Min, + F64x2Min, + F32x4Max, + F64x2Max, + F32x4Add, + F64x2Add, + F32x4Sub, + F64x2Sub, + F32x4Div, + F64x2Div, + F32x4Mul, + F64x2Mul, + F32x4Sqrt, + F64x2Sqrt, + F32x4ConvertSI32x4, + F32x4ConvertUI32x4, + F64x2ConvertSI64x2, + F64x2ConvertUI64x2, + I32x4TruncSF32x4Sat, + I32x4TruncUF32x4Sat, + I64x2TruncSF64x2Sat, + I64x2TruncUF64x2Sat, + + // https://github.com/WebAssembly/bulk-memory-operations + MemoryInit(u32), + MemoryDrop(u32), + MemoryCopy, + MemoryFill, + TableInit(u32), + TableDrop(u32), + TableCopy, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[allow(missing_docs)] +pub struct MemArg { + pub align: u8, + pub offset: u32, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[allow(missing_docs)] +pub struct BrTableData { + pub table: Box<[u32]>, + pub default: u32, +} + +impl Instruction { + /// Is this instruction starts the new block (which should end with terminal instruction). + pub fn is_block(&self) -> bool { + match self { + &Instruction::Block(_) | &Instruction::Loop(_) | &Instruction::If(_) => true, + _ => false, + } + } + + /// Is this instruction determines the termination of instruction sequence? + /// + /// `true` for `Instruction::End` + pub fn is_terminal(&self) -> bool { + match self { + &Instruction::End => true, + _ => false, + } + } +} + +#[allow(missing_docs)] +#[allow(dead_code)] +pub mod opcodes { + pub const UNREACHABLE: u8 = 0x00; + pub const NOP: u8 = 0x01; + pub const BLOCK: u8 = 0x02; + pub const LOOP: u8 = 0x03; + pub const IF: u8 = 0x04; + pub const ELSE: u8 = 0x05; + pub const END: u8 = 0x0b; + pub const BR: u8 = 0x0c; + pub const BRIF: u8 = 0x0d; + pub const BRTABLE: u8 = 0x0e; + pub const RETURN: u8 = 0x0f; + pub const CALL: u8 = 0x10; + pub const CALLINDIRECT: u8 = 0x11; + pub const DROP: u8 = 0x1a; + pub const SELECT: u8 = 0x1b; + pub const GETLOCAL: u8 = 0x20; + pub const SETLOCAL: u8 = 0x21; + pub const TEELOCAL: u8 = 0x22; + pub const GETGLOBAL: u8 = 0x23; + pub const SETGLOBAL: u8 = 0x24; + pub const I32LOAD: u8 = 0x28; + pub const I64LOAD: u8 = 0x29; + pub const F32LOAD: u8 = 0x2a; + pub const F64LOAD: u8 = 0x2b; + pub const I32LOAD8S: u8 = 0x2c; + pub const I32LOAD8U: u8 = 0x2d; + pub const I32LOAD16S: u8 = 0x2e; + pub const I32LOAD16U: u8 = 0x2f; + pub const I64LOAD8S: u8 = 0x30; + pub const I64LOAD8U: u8 = 0x31; + pub const I64LOAD16S: u8 = 0x32; + pub const I64LOAD16U: u8 = 0x33; + pub const I64LOAD32S: u8 = 0x34; + pub const I64LOAD32U: u8 = 0x35; + pub const I32STORE: u8 = 0x36; + pub const I64STORE: u8 = 0x37; + pub const F32STORE: u8 = 0x38; + pub const F64STORE: u8 = 0x39; + pub const I32STORE8: u8 = 0x3a; + pub const I32STORE16: u8 = 0x3b; + pub const I64STORE8: u8 = 0x3c; + pub const I64STORE16: u8 = 0x3d; + pub const I64STORE32: u8 = 0x3e; + pub const CURRENTMEMORY: u8 = 0x3f; + pub const GROWMEMORY: u8 = 0x40; + pub const I32CONST: u8 = 0x41; + pub const I64CONST: u8 = 0x42; + pub const F32CONST: u8 = 0x43; + pub const F64CONST: u8 = 0x44; + pub const I32EQZ: u8 = 0x45; + pub const I32EQ: u8 = 0x46; + pub const I32NE: u8 = 0x47; + pub const I32LTS: u8 = 0x48; + pub const I32LTU: u8 = 0x49; + pub const I32GTS: u8 = 0x4a; + pub const I32GTU: u8 = 0x4b; + pub const I32LES: u8 = 0x4c; + pub const I32LEU: u8 = 0x4d; + pub const I32GES: u8 = 0x4e; + pub const I32GEU: u8 = 0x4f; + pub const I64EQZ: u8 = 0x50; + pub const I64EQ: u8 = 0x51; + pub const I64NE: u8 = 0x52; + pub const I64LTS: u8 = 0x53; + pub const I64LTU: u8 = 0x54; + pub const I64GTS: u8 = 0x55; + pub const I64GTU: u8 = 0x56; + pub const I64LES: u8 = 0x57; + pub const I64LEU: u8 = 0x58; + pub const I64GES: u8 = 0x59; + pub const I64GEU: u8 = 0x5a; + + pub const F32EQ: u8 = 0x5b; + pub const F32NE: u8 = 0x5c; + pub const F32LT: u8 = 0x5d; + pub const F32GT: u8 = 0x5e; + pub const F32LE: u8 = 0x5f; + pub const F32GE: u8 = 0x60; + + pub const F64EQ: u8 = 0x61; + pub const F64NE: u8 = 0x62; + pub const F64LT: u8 = 0x63; + pub const F64GT: u8 = 0x64; + pub const F64LE: u8 = 0x65; + pub const F64GE: u8 = 0x66; + + pub const I32CLZ: u8 = 0x67; + pub const I32CTZ: u8 = 0x68; + pub const I32POPCNT: u8 = 0x69; + pub const I32ADD: u8 = 0x6a; + pub const I32SUB: u8 = 0x6b; + pub const I32MUL: u8 = 0x6c; + pub const I32DIVS: u8 = 0x6d; + pub const I32DIVU: u8 = 0x6e; + pub const I32REMS: u8 = 0x6f; + pub const I32REMU: u8 = 0x70; + pub const I32AND: u8 = 0x71; + pub const I32OR: u8 = 0x72; + pub const I32XOR: u8 = 0x73; + pub const I32SHL: u8 = 0x74; + pub const I32SHRS: u8 = 0x75; + pub const I32SHRU: u8 = 0x76; + pub const I32ROTL: u8 = 0x77; + pub const I32ROTR: u8 = 0x78; + + pub const I64CLZ: u8 = 0x79; + pub const I64CTZ: u8 = 0x7a; + pub const I64POPCNT: u8 = 0x7b; + pub const I64ADD: u8 = 0x7c; + pub const I64SUB: u8 = 0x7d; + pub const I64MUL: u8 = 0x7e; + pub const I64DIVS: u8 = 0x7f; + pub const I64DIVU: u8 = 0x80; + pub const I64REMS: u8 = 0x81; + pub const I64REMU: u8 = 0x82; + pub const I64AND: u8 = 0x83; + pub const I64OR: u8 = 0x84; + pub const I64XOR: u8 = 0x85; + pub const I64SHL: u8 = 0x86; + pub const I64SHRS: u8 = 0x87; + pub const I64SHRU: u8 = 0x88; + pub const I64ROTL: u8 = 0x89; + pub const I64ROTR: u8 = 0x8a; + pub const F32ABS: u8 = 0x8b; + pub const F32NEG: u8 = 0x8c; + pub const F32CEIL: u8 = 0x8d; + pub const F32FLOOR: u8 = 0x8e; + pub const F32TRUNC: u8 = 0x8f; + pub const F32NEAREST: u8 = 0x90; + pub const F32SQRT: u8 = 0x91; + pub const F32ADD: u8 = 0x92; + pub const F32SUB: u8 = 0x93; + pub const F32MUL: u8 = 0x94; + pub const F32DIV: u8 = 0x95; + pub const F32MIN: u8 = 0x96; + pub const F32MAX: u8 = 0x97; + pub const F32COPYSIGN: u8 = 0x98; + pub const F64ABS: u8 = 0x99; + pub const F64NEG: u8 = 0x9a; + pub const F64CEIL: u8 = 0x9b; + pub const F64FLOOR: u8 = 0x9c; + pub const F64TRUNC: u8 = 0x9d; + pub const F64NEAREST: u8 = 0x9e; + pub const F64SQRT: u8 = 0x9f; + pub const F64ADD: u8 = 0xa0; + pub const F64SUB: u8 = 0xa1; + pub const F64MUL: u8 = 0xa2; + pub const F64DIV: u8 = 0xa3; + pub const F64MIN: u8 = 0xa4; + pub const F64MAX: u8 = 0xa5; + pub const F64COPYSIGN: u8 = 0xa6; + + pub const I32WRAPI64: u8 = 0xa7; + pub const I32TRUNCSF32: u8 = 0xa8; + pub const I32TRUNCUF32: u8 = 0xa9; + pub const I32TRUNCSF64: u8 = 0xaa; + pub const I32TRUNCUF64: u8 = 0xab; + pub const I64EXTENDSI32: u8 = 0xac; + pub const I64EXTENDUI32: u8 = 0xad; + pub const I64TRUNCSF32: u8 = 0xae; + pub const I64TRUNCUF32: u8 = 0xaf; + pub const I64TRUNCSF64: u8 = 0xb0; + pub const I64TRUNCUF64: u8 = 0xb1; + pub const F32CONVERTSI32: u8 = 0xb2; + pub const F32CONVERTUI32: u8 = 0xb3; + pub const F32CONVERTSI64: u8 = 0xb4; + pub const F32CONVERTUI64: u8 = 0xb5; + pub const F32DEMOTEF64: u8 = 0xb6; + pub const F64CONVERTSI32: u8 = 0xb7; + pub const F64CONVERTUI32: u8 = 0xb8; + pub const F64CONVERTSI64: u8 = 0xb9; + pub const F64CONVERTUI64: u8 = 0xba; + pub const F64PROMOTEF32: u8 = 0xbb; + + pub const I32REINTERPRETF32: u8 = 0xbc; + pub const I64REINTERPRETF64: u8 = 0xbd; + pub const F32REINTERPRETI32: u8 = 0xbe; + pub const F64REINTERPRETI64: u8 = 0xbf; + + pub const I32_EXTEND8_S: u8 = 0xc0; + pub const I32_EXTEND16_S: u8 = 0xc1; + pub const I64_EXTEND8_S: u8 = 0xc2; + pub const I64_EXTEND16_S: u8 = 0xc3; + pub const I64_EXTEND32_S: u8 = 0xc4; + + pub const ATOMIC_PREFIX: u8 = 0xfe; + pub const ATOMIC_WAKE: u8 = 0x00; + pub const I32_ATOMIC_WAIT: u8 = 0x01; + pub const I64_ATOMIC_WAIT: u8 = 0x02; + + pub const I32_ATOMIC_LOAD: u8 = 0x10; + pub const I64_ATOMIC_LOAD: u8 = 0x11; + pub const I32_ATOMIC_LOAD8U: u8 = 0x12; + pub const I32_ATOMIC_LOAD16U: u8 = 0x13; + pub const I64_ATOMIC_LOAD8U: u8 = 0x14; + pub const I64_ATOMIC_LOAD16U: u8 = 0x15; + pub const I64_ATOMIC_LOAD32U: u8 = 0x16; + pub const I32_ATOMIC_STORE: u8 = 0x17; + pub const I64_ATOMIC_STORE: u8 = 0x18; + pub const I32_ATOMIC_STORE8U: u8 = 0x19; + pub const I32_ATOMIC_STORE16U: u8 = 0x1a; + pub const I64_ATOMIC_STORE8U: u8 = 0x1b; + pub const I64_ATOMIC_STORE16U: u8 = 0x1c; + pub const I64_ATOMIC_STORE32U: u8 = 0x1d; + + pub const I32_ATOMIC_RMW_ADD: u8 = 0x1e; + pub const I64_ATOMIC_RMW_ADD: u8 = 0x1f; + pub const I32_ATOMIC_RMW_ADD8U: u8 = 0x20; + pub const I32_ATOMIC_RMW_ADD16U: u8 = 0x21; + pub const I64_ATOMIC_RMW_ADD8U: u8 = 0x22; + pub const I64_ATOMIC_RMW_ADD16U: u8 = 0x23; + pub const I64_ATOMIC_RMW_ADD32U: u8 = 0x24; + + pub const I32_ATOMIC_RMW_SUB: u8 = 0x25; + pub const I64_ATOMIC_RMW_SUB: u8 = 0x26; + pub const I32_ATOMIC_RMW_SUB8U: u8 = 0x27; + pub const I32_ATOMIC_RMW_SUB16U: u8 = 0x28; + pub const I64_ATOMIC_RMW_SUB8U: u8 = 0x29; + pub const I64_ATOMIC_RMW_SUB16U: u8 = 0x2a; + pub const I64_ATOMIC_RMW_SUB32U: u8 = 0x2b; + + pub const I32_ATOMIC_RMW_AND: u8 = 0x2c; + pub const I64_ATOMIC_RMW_AND: u8 = 0x2d; + pub const I32_ATOMIC_RMW_AND8U: u8 = 0x2e; + pub const I32_ATOMIC_RMW_AND16U: u8 = 0x2f; + pub const I64_ATOMIC_RMW_AND8U: u8 = 0x30; + pub const I64_ATOMIC_RMW_AND16U: u8 = 0x31; + pub const I64_ATOMIC_RMW_AND32U: u8 = 0x32; + + pub const I32_ATOMIC_RMW_OR: u8 = 0x33; + pub const I64_ATOMIC_RMW_OR: u8 = 0x34; + pub const I32_ATOMIC_RMW_OR8U: u8 = 0x35; + pub const I32_ATOMIC_RMW_OR16U: u8 = 0x36; + pub const I64_ATOMIC_RMW_OR8U: u8 = 0x37; + pub const I64_ATOMIC_RMW_OR16U: u8 = 0x38; + pub const I64_ATOMIC_RMW_OR32U: u8 = 0x39; + + pub const I32_ATOMIC_RMW_XOR: u8 = 0x3a; + pub const I64_ATOMIC_RMW_XOR: u8 = 0x3b; + pub const I32_ATOMIC_RMW_XOR8U: u8 = 0x3c; + pub const I32_ATOMIC_RMW_XOR16U: u8 = 0x3d; + pub const I64_ATOMIC_RMW_XOR8U: u8 = 0x3e; + pub const I64_ATOMIC_RMW_XOR16U: u8 = 0x3f; + pub const I64_ATOMIC_RMW_XOR32U: u8 = 0x40; + + pub const I32_ATOMIC_RMW_XCHG: u8 = 0x41; + pub const I64_ATOMIC_RMW_XCHG: u8 = 0x42; + pub const I32_ATOMIC_RMW_XCHG8U: u8 = 0x43; + pub const I32_ATOMIC_RMW_XCHG16U: u8 = 0x44; + pub const I64_ATOMIC_RMW_XCHG8U: u8 = 0x45; + pub const I64_ATOMIC_RMW_XCHG16U: u8 = 0x46; + pub const I64_ATOMIC_RMW_XCHG32U: u8 = 0x47; + + pub const I32_ATOMIC_RMW_CMPXCHG: u8 = 0x48; + pub const I64_ATOMIC_RMW_CMPXCHG: u8 = 0x49; + pub const I32_ATOMIC_RMW_CMPXCHG8U: u8 = 0x4a; + pub const I32_ATOMIC_RMW_CMPXCHG16U: u8 = 0x4b; + pub const I64_ATOMIC_RMW_CMPXCHG8U: u8 = 0x4c; + pub const I64_ATOMIC_RMW_CMPXCHG16U: u8 = 0x4d; + pub const I64_ATOMIC_RMW_CMPXCHG32U: u8 = 0x4e; + + // https://github.com/WebAssembly/simd/blob/master/proposals/simd/BinarySIMD.md + pub const SIMD_PREFIX: u8 = 0xfd; + + pub const V128_LOAD: u32 = 0x00; + pub const V128_STORE: u32 = 0x01; + pub const V128_CONST: u32 = 0x02; + pub const V8X16_SHUFFLE: u32 = 0x03; + + pub const I8X16_SPLAT: u32 = 0x04; + pub const I8X16_EXTRACT_LANE_S: u32 = 0x05; + pub const I8X16_EXTRACT_LANE_U: u32 = 0x06; + pub const I8X16_REPLACE_LANE: u32 = 0x07; + pub const I16X8_SPLAT: u32 = 0x08; + pub const I16X8_EXTRACT_LANE_S: u32 = 0x09; + pub const I16X8_EXTRACT_LANE_U: u32 = 0xa; + pub const I16X8_REPLACE_LANE: u32 = 0x0b; + pub const I32X4_SPLAT: u32 = 0x0c; + pub const I32X4_EXTRACT_LANE: u32 = 0x0d; + pub const I32X4_REPLACE_LANE: u32 = 0x0e; + pub const I64X2_SPLAT: u32 = 0x0f; + pub const I64X2_EXTRACT_LANE: u32 = 0x10; + pub const I64X2_REPLACE_LANE: u32 = 0x11; + pub const F32X4_SPLAT: u32 = 0x12; + pub const F32X4_EXTRACT_LANE: u32 = 0x13; + pub const F32X4_REPLACE_LANE: u32 = 0x14; + pub const F64X2_SPLAT: u32 = 0x15; + pub const F64X2_EXTRACT_LANE: u32 = 0x16; + pub const F64X2_REPLACE_LANE: u32 = 0x17; + + pub const I8X16_EQ: u32 = 0x18; + pub const I8X16_NE: u32 = 0x19; + pub const I8X16_LT_S: u32 = 0x1a; + pub const I8X16_LT_U: u32 = 0x1b; + pub const I8X16_GT_S: u32 = 0x1c; + pub const I8X16_GT_U: u32 = 0x1d; + pub const I8X16_LE_S: u32 = 0x1e; + pub const I8X16_LE_U: u32 = 0x1f; + pub const I8X16_GE_S: u32 = 0x20; + pub const I8X16_GE_U: u32 = 0x21; + + pub const I16X8_EQ: u32 = 0x22; + pub const I16X8_NE: u32 = 0x23; + pub const I16X8_LT_S: u32 = 0x24; + pub const I16X8_LT_U: u32 = 0x25; + pub const I16X8_GT_S: u32 = 0x26; + pub const I16X8_GT_U: u32 = 0x27; + pub const I16X8_LE_S: u32 = 0x28; + pub const I16X8_LE_U: u32 = 0x29; + pub const I16X8_GE_S: u32 = 0x2a; + pub const I16X8_GE_U: u32 = 0x2b; + + pub const I32X4_EQ: u32 = 0x2c; + pub const I32X4_NE: u32 = 0x2d; + pub const I32X4_LT_S: u32 = 0x2e; + pub const I32X4_LT_U: u32 = 0x2f; + pub const I32X4_GT_S: u32 = 0x30; + pub const I32X4_GT_U: u32 = 0x31; + pub const I32X4_LE_S: u32 = 0x32; + pub const I32X4_LE_U: u32 = 0x33; + pub const I32X4_GE_S: u32 = 0x34; + pub const I32X4_GE_U: u32 = 0x35; + + pub const F32X4_EQ: u32 = 0x40; + pub const F32X4_NE: u32 = 0x41; + pub const F32X4_LT: u32 = 0x42; + pub const F32X4_GT: u32 = 0x43; + pub const F32X4_LE: u32 = 0x44; + pub const F32X4_GE: u32 = 0x45; + + pub const F64X2_EQ: u32 = 0x46; + pub const F64X2_NE: u32 = 0x47; + pub const F64X2_LT: u32 = 0x48; + pub const F64X2_GT: u32 = 0x49; + pub const F64X2_LE: u32 = 0x4a; + pub const F64X2_GE: u32 = 0x4b; + + pub const V128_NOT: u32 = 0x4c; + pub const V128_AND: u32 = 0x4d; + pub const V128_OR: u32 = 0x4e; + pub const V128_XOR: u32 = 0x4f; + pub const V128_BITSELECT: u32 = 0x50; + + pub const I8X16_NEG: u32 = 0x51; + pub const I8X16_ANY_TRUE: u32 = 0x52; + pub const I8X16_ALL_TRUE: u32 = 0x53; + pub const I8X16_SHL: u32 = 0x54; + pub const I8X16_SHR_S: u32 = 0x55; + pub const I8X16_SHR_U: u32 = 0x56; + pub const I8X16_ADD: u32 = 0x57; + pub const I8X16_ADD_SATURATE_S: u32 = 0x58; + pub const I8X16_ADD_SATURATE_U: u32 = 0x59; + pub const I8X16_SUB: u32 = 0x5a; + pub const I8X16_SUB_SATURATE_S: u32 = 0x5b; + pub const I8X16_SUB_SATURATE_U: u32 = 0x5c; + pub const I8X16_MUL: u32 = 0x5d; + + pub const I16X8_NEG: u32 = 0x62; + pub const I16X8_ANY_TRUE: u32 = 0x63; + pub const I16X8_ALL_TRUE: u32 = 0x64; + pub const I16X8_SHL: u32 = 0x65; + pub const I16X8_SHR_S: u32 = 0x66; + pub const I16X8_SHR_U: u32 = 0x67; + pub const I16X8_ADD: u32 = 0x68; + pub const I16X8_ADD_SATURATE_S: u32 = 0x69; + pub const I16X8_ADD_SATURATE_U: u32 = 0x6a; + pub const I16X8_SUB: u32 = 0x6b; + pub const I16X8_SUB_SATURATE_S: u32 = 0x6c; + pub const I16X8_SUB_SATURATE_U: u32 = 0x6d; + pub const I16X8_MUL: u32 = 0x6e; + + pub const I32X4_NEG: u32 = 0x73; + pub const I32X4_ANY_TRUE: u32 = 0x74; + pub const I32X4_ALL_TRUE: u32 = 0x75; + pub const I32X4_SHL: u32 = 0x76; + pub const I32X4_SHR_S: u32 = 0x77; + pub const I32X4_SHR_U: u32 = 0x78; + pub const I32X4_ADD: u32 = 0x79; + pub const I32X4_ADD_SATURATE_S: u32 = 0x7a; + pub const I32X4_ADD_SATURATE_U: u32 = 0x7b; + pub const I32X4_SUB: u32 = 0x7c; + pub const I32X4_SUB_SATURATE_S: u32 = 0x7d; + pub const I32X4_SUB_SATURATE_U: u32 = 0x7e; + pub const I32X4_MUL: u32 = 0x7f; + + pub const I64X2_NEG: u32 = 0x84; + pub const I64X2_ANY_TRUE: u32 = 0x85; + pub const I64X2_ALL_TRUE: u32 = 0x86; + pub const I64X2_SHL: u32 = 0x87; + pub const I64X2_SHR_S: u32 = 0x88; + pub const I64X2_SHR_U: u32 = 0x89; + pub const I64X2_ADD: u32 = 0x8a; + pub const I64X2_SUB: u32 = 0x8d; + + pub const F32X4_ABS: u32 = 0x95; + pub const F32X4_NEG: u32 = 0x96; + pub const F32X4_SQRT: u32 = 0x97; + pub const F32X4_ADD: u32 = 0x9a; + pub const F32X4_SUB: u32 = 0x9b; + pub const F32X4_MUL: u32 = 0x9c; + pub const F32X4_DIV: u32 = 0x9d; + pub const F32X4_MIN: u32 = 0x9e; + pub const F32X4_MAX: u32 = 0x9f; + + pub const F64X2_ABS: u32 = 0xa0; + pub const F64X2_NEG: u32 = 0xa1; + pub const F64X2_SQRT: u32 = 0xa2; + pub const F64X2_ADD: u32 = 0xa5; + pub const F64X2_SUB: u32 = 0xa6; + pub const F64X2_MUL: u32 = 0xa7; + pub const F64X2_DIV: u32 = 0xa8; + pub const F64X2_MIN: u32 = 0xa9; + pub const F64X2_MAX: u32 = 0xaa; + + pub const I32X4_TRUNC_S_F32X4_SAT: u32 = 0xab; + pub const I32X4_TRUNC_U_F32X4_SAT: u32 = 0xac; + pub const I64X2_TRUNC_S_F64X2_SAT: u32 = 0xad; + pub const I64X2_TRUNC_U_F64X2_SAT: u32 = 0xae; + + pub const F32X4_CONVERT_S_I32X4: u32 = 0xaf; + pub const F32X4_CONVERT_U_I32X4: u32 = 0xb0; + pub const F64X2_CONVERT_S_I64X2: u32 = 0xb1; + pub const F64X2_CONVERT_U_I64X2: u32 = 0xb2; + + pub const BULK_PREFIX: u8 = 0xfc; + pub const MEMORY_INIT: u8 = 0x08; + pub const MEMORY_DROP: u8 = 0x09; + pub const MEMORY_COPY: u8 = 0x0a; + pub const MEMORY_FILL: u8 = 0x0b; + pub const TABLE_INIT: u8 = 0x0c; + pub const TABLE_DROP: u8 = 0x0d; + pub const TABLE_COPY: u8 = 0x0e; +} + +impl Deserialize for Instruction { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + use self::Instruction::*; + use self::opcodes::*; + + let val: u8 = Uint8::deserialize(reader)?.into(); + + Ok( + match val { + UNREACHABLE => Unreachable, + NOP => Nop, + BLOCK => Block(BlockType::deserialize(reader)?), + LOOP => Loop(BlockType::deserialize(reader)?), + IF => If(BlockType::deserialize(reader)?), + ELSE => Else, + END => End, + + BR => Br(VarUint32::deserialize(reader)?.into()), + BRIF => BrIf(VarUint32::deserialize(reader)?.into()), + BRTABLE => { + let t1: Vec = CountedList::::deserialize(reader)? + .into_inner() + .into_iter() + .map(Into::into) + .collect(); + + BrTable(Box::new(BrTableData { + table: t1.into_boxed_slice(), + default: VarUint32::deserialize(reader)?.into(), + })) + }, + RETURN => Return, + CALL => Call(VarUint32::deserialize(reader)?.into()), + CALLINDIRECT => { + let signature: u32 = VarUint32::deserialize(reader)?.into(); + let table_ref: u8 = Uint8::deserialize(reader)?.into(); + if table_ref != 0 { return Err(Error::InvalidTableReference(table_ref)); } + + CallIndirect( + signature, + table_ref, + ) + }, + DROP => Drop, + SELECT => Select, + + GETLOCAL => GetLocal(VarUint32::deserialize(reader)?.into()), + SETLOCAL => SetLocal(VarUint32::deserialize(reader)?.into()), + TEELOCAL => TeeLocal(VarUint32::deserialize(reader)?.into()), + GETGLOBAL => GetGlobal(VarUint32::deserialize(reader)?.into()), + SETGLOBAL => SetGlobal(VarUint32::deserialize(reader)?.into()), + + I32LOAD => I32Load( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + I64LOAD => I64Load( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + F32LOAD => F32Load( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + F64LOAD => F64Load( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + I32LOAD8S => I32Load8S( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + I32LOAD8U => I32Load8U( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + I32LOAD16S => I32Load16S( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + I32LOAD16U => I32Load16U( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + I64LOAD8S => I64Load8S( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + I64LOAD8U => I64Load8U( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + I64LOAD16S => I64Load16S( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + I64LOAD16U => I64Load16U( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + I64LOAD32S => I64Load32S( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + I64LOAD32U => I64Load32U( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + I32STORE => I32Store( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + I64STORE => I64Store( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + F32STORE => F32Store( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + F64STORE => F64Store( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + I32STORE8 => I32Store8( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + I32STORE16 => I32Store16( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + I64STORE8 => I64Store8( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + I64STORE16 => I64Store16( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + I64STORE32 => I64Store32( + VarUint32::deserialize(reader)?.into(), + VarUint32::deserialize(reader)?.into()), + + + CURRENTMEMORY => { + let mem_ref: u8 = Uint8::deserialize(reader)?.into(); + if mem_ref != 0 { return Err(Error::InvalidMemoryReference(mem_ref)); } + CurrentMemory(mem_ref) + }, + GROWMEMORY => { + let mem_ref: u8 = Uint8::deserialize(reader)?.into(); + if mem_ref != 0 { return Err(Error::InvalidMemoryReference(mem_ref)); } + GrowMemory(mem_ref) + } + + I32CONST => I32Const(VarInt32::deserialize(reader)?.into()), + I64CONST => I64Const(VarInt64::deserialize(reader)?.into()), + F32CONST => F32Const(Uint32::deserialize(reader)?.into()), + F64CONST => F64Const(Uint64::deserialize(reader)?.into()), + I32EQZ => I32Eqz, + I32EQ => I32Eq, + I32NE => I32Ne, + I32LTS => I32LtS, + I32LTU => I32LtU, + I32GTS => I32GtS, + I32GTU => I32GtU, + I32LES => I32LeS, + I32LEU => I32LeU, + I32GES => I32GeS, + I32GEU => I32GeU, + + I64EQZ => I64Eqz, + I64EQ => I64Eq, + I64NE => I64Ne, + I64LTS => I64LtS, + I64LTU => I64LtU, + I64GTS => I64GtS, + I64GTU => I64GtU, + I64LES => I64LeS, + I64LEU => I64LeU, + I64GES => I64GeS, + I64GEU => I64GeU, + + F32EQ => F32Eq, + F32NE => F32Ne, + F32LT => F32Lt, + F32GT => F32Gt, + F32LE => F32Le, + F32GE => F32Ge, + + F64EQ => F64Eq, + F64NE => F64Ne, + F64LT => F64Lt, + F64GT => F64Gt, + F64LE => F64Le, + F64GE => F64Ge, + + I32CLZ => I32Clz, + I32CTZ => I32Ctz, + I32POPCNT => I32Popcnt, + I32ADD => I32Add, + I32SUB => I32Sub, + I32MUL => I32Mul, + I32DIVS => I32DivS, + I32DIVU => I32DivU, + I32REMS => I32RemS, + I32REMU => I32RemU, + I32AND => I32And, + I32OR => I32Or, + I32XOR => I32Xor, + I32SHL => I32Shl, + I32SHRS => I32ShrS, + I32SHRU => I32ShrU, + I32ROTL => I32Rotl, + I32ROTR => I32Rotr, + + I64CLZ => I64Clz, + I64CTZ => I64Ctz, + I64POPCNT => I64Popcnt, + I64ADD => I64Add, + I64SUB => I64Sub, + I64MUL => I64Mul, + I64DIVS => I64DivS, + I64DIVU => I64DivU, + I64REMS => I64RemS, + I64REMU => I64RemU, + I64AND => I64And, + I64OR => I64Or, + I64XOR => I64Xor, + I64SHL => I64Shl, + I64SHRS => I64ShrS, + I64SHRU => I64ShrU, + I64ROTL => I64Rotl, + I64ROTR => I64Rotr, + F32ABS => F32Abs, + F32NEG => F32Neg, + F32CEIL => F32Ceil, + F32FLOOR => F32Floor, + F32TRUNC => F32Trunc, + F32NEAREST => F32Nearest, + F32SQRT => F32Sqrt, + F32ADD => F32Add, + F32SUB => F32Sub, + F32MUL => F32Mul, + F32DIV => F32Div, + F32MIN => F32Min, + F32MAX => F32Max, + F32COPYSIGN => F32Copysign, + F64ABS => F64Abs, + F64NEG => F64Neg, + F64CEIL => F64Ceil, + F64FLOOR => F64Floor, + F64TRUNC => F64Trunc, + F64NEAREST => F64Nearest, + F64SQRT => F64Sqrt, + F64ADD => F64Add, + F64SUB => F64Sub, + F64MUL => F64Mul, + F64DIV => F64Div, + F64MIN => F64Min, + F64MAX => F64Max, + F64COPYSIGN => F64Copysign, + + I32WRAPI64 => I32WrapI64, + I32TRUNCSF32 => I32TruncSF32, + I32TRUNCUF32 => I32TruncUF32, + I32TRUNCSF64 => I32TruncSF64, + I32TRUNCUF64 => I32TruncUF64, + I64EXTENDSI32 => I64ExtendSI32, + I64EXTENDUI32 => I64ExtendUI32, + I64TRUNCSF32 => I64TruncSF32, + I64TRUNCUF32 => I64TruncUF32, + I64TRUNCSF64 => I64TruncSF64, + I64TRUNCUF64 => I64TruncUF64, + F32CONVERTSI32 => F32ConvertSI32, + F32CONVERTUI32 => F32ConvertUI32, + F32CONVERTSI64 => F32ConvertSI64, + F32CONVERTUI64 => F32ConvertUI64, + F32DEMOTEF64 => F32DemoteF64, + F64CONVERTSI32 => F64ConvertSI32, + F64CONVERTUI32 => F64ConvertUI32, + F64CONVERTSI64 => F64ConvertSI64, + F64CONVERTUI64 => F64ConvertUI64, + F64PROMOTEF32 => F64PromoteF32, + + I32REINTERPRETF32 => I32ReinterpretF32, + I64REINTERPRETF64 => I64ReinterpretF64, + F32REINTERPRETI32 => F32ReinterpretI32, + F64REINTERPRETI64 => F64ReinterpretI64, + I32_EXTEND8_S => I32Extend8S, + I32_EXTEND16_S => I32Extend16S, + I64_EXTEND8_S => I64Extend8S, + I64_EXTEND16_S => I64Extend16S, + I64_EXTEND32_S => I64Extend32S, + + ATOMIC_PREFIX => return deserialize_atomic(reader), + SIMD_PREFIX => return deserialize_simd(reader), + + BULK_PREFIX => return deserialize_bulk(reader), + + _ => { return Err(Error::UnknownOpcode(val)); } + } + ) + } +} + +fn deserialize_atomic(reader: &mut R) -> Result { + use self::Instruction::*; + use self::opcodes::*; + + let val: u8 = Uint8::deserialize(reader)?.into(); + let mem = MemArg::deserialize(reader)?; + Ok(match val { + ATOMIC_WAKE => AtomicWake(mem), + I32_ATOMIC_WAIT => I32AtomicWait(mem), + I64_ATOMIC_WAIT => I64AtomicWait(mem), + + I32_ATOMIC_LOAD => I32AtomicLoad(mem), + I64_ATOMIC_LOAD => I64AtomicLoad(mem), + I32_ATOMIC_LOAD8U => I32AtomicLoad8u(mem), + I32_ATOMIC_LOAD16U => I32AtomicLoad16u(mem), + I64_ATOMIC_LOAD8U => I64AtomicLoad8u(mem), + I64_ATOMIC_LOAD16U => I64AtomicLoad16u(mem), + I64_ATOMIC_LOAD32U => I64AtomicLoad32u(mem), + I32_ATOMIC_STORE => I32AtomicStore(mem), + I64_ATOMIC_STORE => I64AtomicStore(mem), + I32_ATOMIC_STORE8U => I32AtomicStore8u(mem), + I32_ATOMIC_STORE16U => I32AtomicStore16u(mem), + I64_ATOMIC_STORE8U => I64AtomicStore8u(mem), + I64_ATOMIC_STORE16U => I64AtomicStore16u(mem), + I64_ATOMIC_STORE32U => I64AtomicStore32u(mem), + + I32_ATOMIC_RMW_ADD => I32AtomicRmwAdd(mem), + I64_ATOMIC_RMW_ADD => I64AtomicRmwAdd(mem), + I32_ATOMIC_RMW_ADD8U => I32AtomicRmwAdd8u(mem), + I32_ATOMIC_RMW_ADD16U => I32AtomicRmwAdd16u(mem), + I64_ATOMIC_RMW_ADD8U => I64AtomicRmwAdd8u(mem), + I64_ATOMIC_RMW_ADD16U => I64AtomicRmwAdd16u(mem), + I64_ATOMIC_RMW_ADD32U => I64AtomicRmwAdd32u(mem), + + I32_ATOMIC_RMW_SUB => I32AtomicRmwSub(mem), + I64_ATOMIC_RMW_SUB => I64AtomicRmwSub(mem), + I32_ATOMIC_RMW_SUB8U => I32AtomicRmwSub8u(mem), + I32_ATOMIC_RMW_SUB16U => I32AtomicRmwSub16u(mem), + I64_ATOMIC_RMW_SUB8U => I64AtomicRmwSub8u(mem), + I64_ATOMIC_RMW_SUB16U => I64AtomicRmwSub16u(mem), + I64_ATOMIC_RMW_SUB32U => I64AtomicRmwSub32u(mem), + + I32_ATOMIC_RMW_OR => I32AtomicRmwOr(mem), + I64_ATOMIC_RMW_OR => I64AtomicRmwOr(mem), + I32_ATOMIC_RMW_OR8U => I32AtomicRmwOr8u(mem), + I32_ATOMIC_RMW_OR16U => I32AtomicRmwOr16u(mem), + I64_ATOMIC_RMW_OR8U => I64AtomicRmwOr8u(mem), + I64_ATOMIC_RMW_OR16U => I64AtomicRmwOr16u(mem), + I64_ATOMIC_RMW_OR32U => I64AtomicRmwOr32u(mem), + + I32_ATOMIC_RMW_XOR => I32AtomicRmwXor(mem), + I64_ATOMIC_RMW_XOR => I64AtomicRmwXor(mem), + I32_ATOMIC_RMW_XOR8U => I32AtomicRmwXor8u(mem), + I32_ATOMIC_RMW_XOR16U => I32AtomicRmwXor16u(mem), + I64_ATOMIC_RMW_XOR8U => I64AtomicRmwXor8u(mem), + I64_ATOMIC_RMW_XOR16U => I64AtomicRmwXor16u(mem), + I64_ATOMIC_RMW_XOR32U => I64AtomicRmwXor32u(mem), + + I32_ATOMIC_RMW_XCHG => I32AtomicRmwXchg(mem), + I64_ATOMIC_RMW_XCHG => I64AtomicRmwXchg(mem), + I32_ATOMIC_RMW_XCHG8U => I32AtomicRmwXchg8u(mem), + I32_ATOMIC_RMW_XCHG16U => I32AtomicRmwXchg16u(mem), + I64_ATOMIC_RMW_XCHG8U => I64AtomicRmwXchg8u(mem), + I64_ATOMIC_RMW_XCHG16U => I64AtomicRmwXchg16u(mem), + I64_ATOMIC_RMW_XCHG32U => I64AtomicRmwXchg32u(mem), + + I32_ATOMIC_RMW_CMPXCHG => I32AtomicRmwCmpxchg(mem), + I64_ATOMIC_RMW_CMPXCHG => I64AtomicRmwCmpxchg(mem), + I32_ATOMIC_RMW_CMPXCHG8U => I32AtomicRmwCmpxchg8u(mem), + I32_ATOMIC_RMW_CMPXCHG16U => I32AtomicRmwCmpxchg16u(mem), + I64_ATOMIC_RMW_CMPXCHG8U => I64AtomicRmwCmpxchg8u(mem), + I64_ATOMIC_RMW_CMPXCHG16U => I64AtomicRmwCmpxchg16u(mem), + I64_ATOMIC_RMW_CMPXCHG32U => I64AtomicRmwCmpxchg32u(mem), + + _ => return Err(Error::UnknownOpcode(val)), + }) +} + +fn deserialize_simd(reader: &mut R) -> Result { + use self::Instruction::*; + use self::opcodes::*; + + let val = VarUint32::deserialize(reader)?.into(); + Ok(match val { + V128_CONST => { + let mut buf = [0; 16]; + reader.read(&mut buf)?; + V128Const(Box::new(buf)) + } + V128_LOAD => V128Load(MemArg::deserialize(reader)?), + V128_STORE => V128Store(MemArg::deserialize(reader)?), + I8X16_SPLAT => I8x16Splat, + I16X8_SPLAT => I16x8Splat, + I32X4_SPLAT => I32x4Splat, + I64X2_SPLAT => I64x2Splat, + F32X4_SPLAT => F32x4Splat, + F64X2_SPLAT => F64x2Splat, + I8X16_EXTRACT_LANE_S => I8x16ExtractLaneS(Uint8::deserialize(reader)?.into()), + I8X16_EXTRACT_LANE_U => I8x16ExtractLaneU(Uint8::deserialize(reader)?.into()), + I16X8_EXTRACT_LANE_S => I16x8ExtractLaneS(Uint8::deserialize(reader)?.into()), + I16X8_EXTRACT_LANE_U => I16x8ExtractLaneU(Uint8::deserialize(reader)?.into()), + I32X4_EXTRACT_LANE => I32x4ExtractLane(Uint8::deserialize(reader)?.into()), + I64X2_EXTRACT_LANE => I64x2ExtractLane(Uint8::deserialize(reader)?.into()), + F32X4_EXTRACT_LANE => F32x4ExtractLane(Uint8::deserialize(reader)?.into()), + F64X2_EXTRACT_LANE => F64x2ExtractLane(Uint8::deserialize(reader)?.into()), + I8X16_REPLACE_LANE => I8x16ReplaceLane(Uint8::deserialize(reader)?.into()), + I16X8_REPLACE_LANE => I16x8ReplaceLane(Uint8::deserialize(reader)?.into()), + I32X4_REPLACE_LANE => I32x4ReplaceLane(Uint8::deserialize(reader)?.into()), + I64X2_REPLACE_LANE => I64x2ReplaceLane(Uint8::deserialize(reader)?.into()), + F32X4_REPLACE_LANE => F32x4ReplaceLane(Uint8::deserialize(reader)?.into()), + F64X2_REPLACE_LANE => F64x2ReplaceLane(Uint8::deserialize(reader)?.into()), + V8X16_SHUFFLE => { + let mut buf = [0; 16]; + reader.read(&mut buf)?; + V8x16Shuffle(Box::new(buf)) + } + I8X16_ADD => I8x16Add, + I16X8_ADD => I16x8Add, + I32X4_ADD => I32x4Add, + I64X2_ADD => I64x2Add, + I8X16_SUB => I8x16Sub, + I16X8_SUB => I16x8Sub, + I32X4_SUB => I32x4Sub, + I64X2_SUB => I64x2Sub, + I8X16_MUL => I8x16Mul, + I16X8_MUL => I16x8Mul, + I32X4_MUL => I32x4Mul, + // I64X2_MUL => I64x2Mul, + I8X16_NEG => I8x16Neg, + I16X8_NEG => I16x8Neg, + I32X4_NEG => I32x4Neg, + I64X2_NEG => I64x2Neg, + + I8X16_ADD_SATURATE_S => I8x16AddSaturateS, + I8X16_ADD_SATURATE_U => I8x16AddSaturateU, + I16X8_ADD_SATURATE_S => I16x8AddSaturateS, + I16X8_ADD_SATURATE_U => I16x8AddSaturateU, + I8X16_SUB_SATURATE_S => I8x16SubSaturateS, + I8X16_SUB_SATURATE_U => I8x16SubSaturateU, + I16X8_SUB_SATURATE_S => I16x8SubSaturateS, + I16X8_SUB_SATURATE_U => I16x8SubSaturateU, + I8X16_SHL => I8x16Shl, + I16X8_SHL => I16x8Shl, + I32X4_SHL => I32x4Shl, + I64X2_SHL => I64x2Shl, + I8X16_SHR_S => I8x16ShrS, + I8X16_SHR_U => I8x16ShrU, + I16X8_SHR_S => I16x8ShrS, + I16X8_SHR_U => I16x8ShrU, + I32X4_SHR_S => I32x4ShrS, + I32X4_SHR_U => I32x4ShrU, + I64X2_SHR_S => I64x2ShrS, + I64X2_SHR_U => I64x2ShrU, + V128_AND => V128And, + V128_OR => V128Or, + V128_XOR => V128Xor, + V128_NOT => V128Not, + V128_BITSELECT => V128Bitselect, + I8X16_ANY_TRUE => I8x16AnyTrue, + I16X8_ANY_TRUE => I16x8AnyTrue, + I32X4_ANY_TRUE => I32x4AnyTrue, + I64X2_ANY_TRUE => I64x2AnyTrue, + I8X16_ALL_TRUE => I8x16AllTrue, + I16X8_ALL_TRUE => I16x8AllTrue, + I32X4_ALL_TRUE => I32x4AllTrue, + I64X2_ALL_TRUE => I64x2AllTrue, + I8X16_EQ => I8x16Eq, + I16X8_EQ => I16x8Eq, + I32X4_EQ => I32x4Eq, + // I64X2_EQ => I64x2Eq, + F32X4_EQ => F32x4Eq, + F64X2_EQ => F64x2Eq, + I8X16_NE => I8x16Ne, + I16X8_NE => I16x8Ne, + I32X4_NE => I32x4Ne, + // I64X2_NE => I64x2Ne, + F32X4_NE => F32x4Ne, + F64X2_NE => F64x2Ne, + I8X16_LT_S => I8x16LtS, + I8X16_LT_U => I8x16LtU, + I16X8_LT_S => I16x8LtS, + I16X8_LT_U => I16x8LtU, + I32X4_LT_S => I32x4LtS, + I32X4_LT_U => I32x4LtU, + // I64X2_LT_S => I64x2LtS, + // I64X2_LT_U => I64x2LtU, + F32X4_LT => F32x4Lt, + F64X2_LT => F64x2Lt, + I8X16_LE_S => I8x16LeS, + I8X16_LE_U => I8x16LeU, + I16X8_LE_S => I16x8LeS, + I16X8_LE_U => I16x8LeU, + I32X4_LE_S => I32x4LeS, + I32X4_LE_U => I32x4LeU, + // I64X2_LE_S => I64x2LeS, + // I64X2_LE_U => I64x2LeU, + F32X4_LE => F32x4Le, + F64X2_LE => F64x2Le, + I8X16_GT_S => I8x16GtS, + I8X16_GT_U => I8x16GtU, + I16X8_GT_S => I16x8GtS, + I16X8_GT_U => I16x8GtU, + I32X4_GT_S => I32x4GtS, + I32X4_GT_U => I32x4GtU, + // I64X2_GT_S => I64x2GtS, + // I64X2_GT_U => I64x2GtU, + F32X4_GT => F32x4Gt, + F64X2_GT => F64x2Gt, + I8X16_GE_S => I8x16GeS, + I8X16_GE_U => I8x16GeU, + I16X8_GE_S => I16x8GeS, + I16X8_GE_U => I16x8GeU, + I32X4_GE_S => I32x4GeS, + I32X4_GE_U => I32x4GeU, + // I64X2_GE_S => I64x2GeS, + // I64X2_GE_U => I64x2GeU, + F32X4_GE => F32x4Ge, + F64X2_GE => F64x2Ge, + F32X4_NEG => F32x4Neg, + F64X2_NEG => F64x2Neg, + F32X4_ABS => F32x4Abs, + F64X2_ABS => F64x2Abs, + F32X4_MIN => F32x4Min, + F64X2_MIN => F64x2Min, + F32X4_MAX => F32x4Max, + F64X2_MAX => F64x2Max, + F32X4_ADD => F32x4Add, + F64X2_ADD => F64x2Add, + F32X4_SUB => F32x4Sub, + F64X2_SUB => F64x2Sub, + F32X4_DIV => F32x4Div, + F64X2_DIV => F64x2Div, + F32X4_MUL => F32x4Mul, + F64X2_MUL => F64x2Mul, + F32X4_SQRT => F32x4Sqrt, + F64X2_SQRT => F64x2Sqrt, + F32X4_CONVERT_S_I32X4 => F32x4ConvertSI32x4, + F32X4_CONVERT_U_I32X4 => F32x4ConvertUI32x4, + F64X2_CONVERT_S_I64X2 => F64x2ConvertSI64x2, + F64X2_CONVERT_U_I64X2 => F64x2ConvertUI64x2, + I32X4_TRUNC_S_F32X4_SAT => I32x4TruncSF32x4Sat, + I32X4_TRUNC_U_F32X4_SAT => I32x4TruncUF32x4Sat, + I64X2_TRUNC_S_F64X2_SAT => I64x2TruncSF64x2Sat, + I64X2_TRUNC_U_F64X2_SAT => I64x2TruncUF64x2Sat, + + _ => return Err(Error::UnknownSimdOpcode(val)), + }) +} + +fn deserialize_bulk(reader: &mut R) -> Result { + use self::Instruction::*; + use self::opcodes::*; + + let val: u8 = Uint8::deserialize(reader)?.into(); + Ok(match val { + MEMORY_INIT => { + if u8::from(Uint8::deserialize(reader)?) != 0 { + return Err(Error::UnknownOpcode(val)) + } + MemoryInit(VarUint32::deserialize(reader)?.into()) + } + MEMORY_DROP => MemoryDrop(VarUint32::deserialize(reader)?.into()), + MEMORY_FILL => { + if u8::from(Uint8::deserialize(reader)?) != 0 { + return Err(Error::UnknownOpcode(val)) + } + MemoryFill + } + MEMORY_COPY => { + if u8::from(Uint8::deserialize(reader)?) != 0 { + return Err(Error::UnknownOpcode(val)) + } + MemoryCopy + } + + TABLE_INIT => { + if u8::from(Uint8::deserialize(reader)?) != 0 { + return Err(Error::UnknownOpcode(val)) + } + TableInit(VarUint32::deserialize(reader)?.into()) + } + TABLE_DROP => TableDrop(VarUint32::deserialize(reader)?.into()), + TABLE_COPY => { + if u8::from(Uint8::deserialize(reader)?) != 0 { + return Err(Error::UnknownOpcode(val)) + } + TableCopy + } + + _ => return Err(Error::UnknownOpcode(val)), + }) +} + +impl Deserialize for MemArg { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let align = Uint8::deserialize(reader)?; + let offset = VarUint32::deserialize(reader)?; + Ok(MemArg { align: align.into(), offset: offset.into() }) + } +} diff --git a/kernel-ewasm/validator/src/io.rs b/kernel-ewasm/validator/src/io.rs new file mode 100644 index 0000000..912075e --- /dev/null +++ b/kernel-ewasm/validator/src/io.rs @@ -0,0 +1,120 @@ +#[cfg(feature="std")] +use std::io; + +#[cfg(not(feature = "std"))] +use pwasm_std::vec::Vec; + +/// IO specific error. +#[derive(Debug)] +pub enum Error { + /// Some unexpected data left in the buffer after reading all data. + TrailingData, + + /// Unexpected End-Of-File + UnexpectedEof, + + /// Invalid data is encountered. + InvalidData, + + #[cfg(feature = "std")] + IoError(io::Error), +} + +/// IO specific Result. +pub type Result = core::result::Result; + +pub trait Write { + /// Write a buffer of data into this write. + /// + /// All data is written at once. + fn write(&mut self, buf: &[u8]) -> Result<()>; +} + +pub trait Read { + /// Read a data from this read to a buffer. + /// + /// If there is not enough data in this read then `UnexpectedEof` will be returned. + fn read(&mut self, buf: &mut [u8]) -> Result<()>; +} + +/// Reader that saves the last position. +pub struct Cursor { + inner: T, + pos: usize, +} + +impl Cursor { + pub fn new(inner: T) -> Cursor { + Cursor { + inner, + pos: 0, + } + } + + pub fn position(&self) -> usize { + self.pos + } +} + +impl> Read for Cursor { + fn read(&mut self, buf: &mut [u8]) -> Result<()> { + let slice = self.inner.as_ref(); + let remainder = slice.len() - self.pos; + let requested = buf.len(); + if requested > remainder { + return Err(Error::UnexpectedEof); + } + buf.copy_from_slice(&slice[self.pos..(self.pos + requested)]); + self.pos += requested; + Ok(()) + } +} + +#[cfg(not(feature = "std"))] +impl Write for Vec { + fn write(&mut self, buf: &[u8]) -> Result<()> { + self.extend(buf); + Ok(()) + } +} + +#[cfg(feature = "std")] +impl Read for T { + fn read(&mut self, buf: &mut [u8]) -> Result<()> { + self.read_exact(buf) + .map_err(Error::IoError) + } +} + +#[cfg(feature = "std")] +impl Write for T { + fn write(&mut self, buf: &[u8]) -> Result<()> { + self.write_all(buf).map_err(Error::IoError) + } +} + +// #[cfg(test)] +// mod tests { +// use super::*; + +// #[test] +// fn cursor() { +// let mut cursor = Cursor::new(vec![0xFFu8, 0x7Fu8]); +// assert_eq!(cursor.position(), 0); + +// let mut buf = [0u8]; +// assert!(cursor.read(&mut buf[..]).is_ok()); +// assert_eq!(cursor.position(), 1); +// assert_eq!(buf[0], 0xFFu8); +// assert!(cursor.read(&mut buf[..]).is_ok()); +// assert_eq!(buf[0], 0x7Fu8); +// assert_eq!(cursor.position(), 2); +// } + +// #[test] +// fn overflow_in_cursor() { +// let mut cursor = Cursor::new(vec![0u8]); +// let mut buf = [0, 1, 2]; +// assert!(cursor.read(&mut buf[..]).is_err()); +// } +// } diff --git a/kernel-ewasm/validator/src/lib.rs b/kernel-ewasm/validator/src/lib.rs new file mode 100644 index 0000000..770c987 --- /dev/null +++ b/kernel-ewasm/validator/src/lib.rs @@ -0,0 +1,339 @@ +//! # Validator +//! +//! Crate for parsing WASM modules and validating pwasm contracts on-chain +//! according to the cap9 spec. This validates the contract in a buffer rather +//! than parsing into native data structure. +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(not(feature = "std"))] +#[macro_use] +extern crate alloc; + +mod func; +mod import_entry; +mod instructions; +mod io; +mod primitives; +mod serialization; +mod types; +use self::serialization::Deserialize; + +use self::primitives::{ + CountedList, Uint32, Uint64, Uint8, VarInt32, VarInt64, VarInt7, VarUint1, VarUint32, VarUint7, +}; + +mod listing; +pub mod modules; +use listing::*; +pub use modules::Function; +pub use modules::Module; + +/// A trait for types which can be validated against the cap9 spec. +pub trait Validity { + /// Tests the object for validity. + fn is_valid(&self) -> bool; +} + +impl<'a> Validity for modules::Module<'a> { + fn is_valid(&self) -> bool { + // Now that we have our hooks into the module, let's iterate over the + // imports to determine white/grey/black listings. We need to remember + // where the function and code data starts. + + // There is only one greylisted item (dcall) so we will just reserve a + // place for that rather than maintain a list. We also want to track the + // function indices of `gasleft` and `sender` for later, as they form + // part of the syscall. + let mut dcall_index: Option = None; + let mut gasleft_index: Option = None; + let mut sender_index: Option = None; + + // Iterate through each of the imports. If we find one of the imports of + // entry (as above) we note its index. If the import is a blacklisted + // import we know immediately that the contract is invalid so we return + // false early. If the import is neither of those (i.e. it's + // whitelisted) we simply skip over it. Import indices come before + // function indices, so we can just consider them the same while we are + // iterating through imports. + if let Some(imports) = self.imports() { + for (index, import) in imports.enumerate() { + if import.mod_name == "env" && import.field_name == "sender" { + if sender_index.is_some() { + panic!("sender imported multiple times"); + } + sender_index = Some(index as usize); + } + + if import.mod_name == "env" && import.field_name == "gasleft" { + if gasleft_index.is_some() { + panic!("gasleft imported multiple times"); + } + gasleft_index = Some(index as usize); + } + + match import.listing() { + Listing::White => (), + Listing::Grey => { + if dcall_index.is_some() { + panic!("dcall imported multiple times"); + } + dcall_index = Some(index as usize); + } + Listing::Black => { + // If we encounter a blacklisted import we can return + // early. + return false; + } + } + } + } + // If there is no dcall imported (therefore dcall_index is + // None) then we know all functions must be valid so we can skip + // iterating through the functions. + if let Some(dcall_i) = dcall_index { + if let Some(funcs) = self.functions() { + // Iterate through each of the functions and determine if it is + // valid. + for (_i, func) in funcs.enumerate() { + // Check if the function is a system call, this is only + // worth doing if we have indices for gasleft and sender, as + // they are necessary for the syscall. + if let (Some(gasleft_i), Some(sender_i)) = (gasleft_index, sender_index) { + if func.is_syscall(dcall_i as u32, gasleft_i as u32, sender_i as u32) { + // If the function is a system call we can continue + // past it as it is valid. + continue; + } + } + // At this point we know that the function is not a syscall. + // We must now check that it has no grey listed calls (i.e. + // dcall). We only care about calls here. + if func.contains_grey_call(dcall_i as u32) { + // This function contains a greylisted call (i.e. + // dcall), so we must return with false as the + // contract is invalid. + return false; + } + } + } + } + // All the tests have passed so we can return true. + true + } +} + +/// Parse a variable size VarUint32 (i.e. LEB) as per the WASM spec. TODO: let's +/// see if we can import this from parity-wasm. +fn parse_varuint_32(cursor: &mut Cursor) -> u32 { + let mut res = 0; + let mut shift = 0; + loop { + if shift > 31 { + panic!("invalid varuint32 {}", shift); + } + let b = cursor.read_ref().unwrap().clone() as u32; + res |= (b & 0x7f).checked_shl(shift).expect("invalid varuint32"); + shift += 7; + if (b >> 7) == 0 { + if shift >= 32 && (b as u8).leading_zeros() < 4 { + panic!("invalid varuint32, b: {}", b); + } + break; + } + } + res +} + +// Seek does not seem to be implemented in core, so we'll reimplement what we +// need. +#[derive(Debug)] +struct Cursor<'a> { + current_offset: usize, + body: &'a [u8], +} + +impl<'a> Cursor<'a> { + // Read the byte at the cusor, and increment the pointer by 1. + fn read_ref(&mut self) -> Option<&'a u8> { + if self.current_offset < self.body.len() { + let val = &self.body[self.current_offset]; + self.current_offset += 1; + Some(val) + } else { + None + } + } + + fn read_ref_n(&mut self, n: usize) -> &'a [u8] { + let val = &self.body[self.current_offset..(self.current_offset + n)]; + self.current_offset += n; + val + } + + fn skip(&mut self, n: usize) { + self.current_offset += n; + } +} + +/// Implement standard read definition (which clones). This is basically the +/// rust definition of read for slice. +impl<'a> io::Read for Cursor<'a> { + fn read(&mut self, buf: &mut [u8]) -> io::Result<()> { + let actual_self = &self.body[self.current_offset..]; + let amt = core::cmp::min(buf.len(), actual_self.len()); + let (a, _) = actual_self.split_at(amt); + + if amt == 1 { + buf[0] = a[0]; + } else { + buf[..amt].copy_from_slice(a); + } + + self.current_offset += amt; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use modules::Module; + use std::fs::File; + use std::io::Read; + use wabt::wat2wasm; + + #[test] + fn module_only_pass() { + let wat = "(module)"; + let wasm = wat2wasm(wat).unwrap(); + let validation_result = Module::new(wasm.as_slice()).is_valid(); + assert_eq!(validation_result, true); + } + + #[test] + fn minimal_contract_pass() { + let wat = r#" +;; Minimal contract +(module + (type $t0 (func)) + (func $call (type $t0) + unreachable) + (export "call" (func $call))) +"#; + let wasm = wat2wasm(wat).unwrap(); + let validation_result = Module::new(wasm.as_slice()).is_valid(); + assert_eq!(validation_result, true); + } + + #[test] + fn example_contract_1_notpass() { + let mut f = File::open( + "../example_contract_1/target/wasm32-unknown-unknown/release/example_contract_1.wasm", + ) + .expect("could not open file"); + let mut wasm = Vec::new(); + f.read_to_end(&mut wasm).unwrap(); + let validation_result = Module::new(wasm.as_slice()).is_valid(); + assert_eq!(validation_result, false); + } + + #[test] + fn raw_kernel_pass() { + let mut f = File::open("../target/wasm32-unknown-unknown/release/kernel_ewasm.wasm") + .expect("could not open file"); + let mut wasm = Vec::new(); + f.read_to_end(&mut wasm).unwrap(); + let validation_result = Module::new(wasm.as_slice()).is_valid(); + // NB: the kernel currently passes because it doesn't do any syscalls. + // This will change. + assert_eq!(validation_result, true); + } + + #[test] + fn with_syscall_compliant_pass() { + let mut f = + File::open("test_files/with_syscall_compliant.wat").expect("could not open file"); + let mut wat = Vec::new(); + f.read_to_end(&mut wat).unwrap(); + let wasm = wat2wasm(wat).unwrap(); + let validation_result = Module::new(wasm.as_slice()).is_valid(); + assert_eq!(validation_result, true); + } + + #[test] + fn with_syscall_noncompliant_notpass() { + let mut f = + File::open("test_files/with_syscall_noncompliant.wat").expect("could not open file"); + let mut wat = Vec::new(); + f.read_to_end(&mut wat).unwrap(); + let wasm = wat2wasm(wat).unwrap(); + let validation_result = Module::new(wasm.as_slice()).is_valid(); + assert_eq!(validation_result, false); + } + + #[test] + fn with_syscall_noncompliant_locals_notpass() { + let mut f = File::open("test_files/with_syscall_noncompliant_locals.wat") + .expect("could not open file"); + let mut wat = Vec::new(); + f.read_to_end(&mut wat).unwrap(); + let wasm = wat2wasm(wat).unwrap(); + let validation_result = Module::new(wasm.as_slice()).is_valid(); + assert_eq!(validation_result, false); + } + + #[test] + fn with_syscall_extra_dcall_notpass() { + let mut f = + File::open("test_files/with_syscall_extra_dcall.wat").expect("could not open file"); + let mut wat = Vec::new(); + f.read_to_end(&mut wat).unwrap(); + let wasm = wat2wasm(wat).unwrap(); + let validation_result = Module::new(wasm.as_slice()).is_valid(); + assert_eq!(validation_result, false); + } + + #[test] + fn minimal_contract_with_write_fail() { + let wat = r#" +;; Minimal contract with a single storage write call +(module + (type $t0 (func)) + (type $t1 (func (param i32 i32))) + (import "env" "storage_write" (func $env.storage_write (type $t1))) + (func $call (type $t0) + i32.const 5 + i32.const 15 + call $env.storage_write + unreachable) + (export "call" (func $call))) +"#; + let wasm = wat2wasm(wat).unwrap(); + let validation_result = Module::new(wasm.as_slice()).is_valid(); + assert_eq!(validation_result, false); + } + + #[test] + fn with_call_indirect_fail() { + let wat = r#" +;; Perform an indirect call via a table +(module + (type $dcall_type (func (param i32 i32))) + (import "env" "dcall" (func $env.dcall (type $dcall_type))) + (table 2 anyfunc) + (func $f1 (result i32) + i32.const 42) + (func $f2 (result i32) + i32.const 13) + (elem (i32.const 0) $f1 $f2) + (type $return_i32 (func (result i32))) + (func (export "callByIndex") (param $i i32) (result i32) + get_local $i + call_indirect (type $return_i32)) +) +"#; + let wasm = wat2wasm(wat).unwrap(); + let validation_result = Module::new(wasm.as_slice()).is_valid(); + assert_eq!(validation_result, false); + } +} diff --git a/kernel-ewasm/validator/src/listing.rs b/kernel-ewasm/validator/src/listing.rs new file mode 100644 index 0000000..d8de061 --- /dev/null +++ b/kernel-ewasm/validator/src/listing.rs @@ -0,0 +1,78 @@ +#[cfg(not(feature = "std"))] +use pwasm_std::Vec; +#[cfg(not(feature = "std"))] +use pwasm_std::String; +/// A listing is a category of import. There are 3 types of imports whitelisted, +/// greylisted, and blacklisted. There is no blacklist, everything that is not +/// whitlisted or greylisted is blacklisted, even if we don't recognise it. +/// +/// * Whitelisted: Functions which can be run with no state effects and we +/// don't care about them. Examples include getting addresses, returning, +/// reverting etc. +/// * Greylisted: Functions that _do_ perform dangerous operations, but that we +/// need for the operation of syscalls etc. These calls need to be +/// surrounded by the correct protections. These are permitted to be +/// imported, but must be checked for safety. +/// * Blacklisted: Everything else. These cannot even be imported. If they are +/// imported the contract is not valid. +#[derive(Debug)] +pub enum Listing { + White, + Grey, + Black, +} + +pub trait Listed { + fn listing(&self) -> Listing; +} + +#[derive(Debug, Clone)] +pub struct ImportEntry { + pub mod_name: String, + pub field_name: String, +} + +impl Listed for ImportEntry { + fn listing(&self) -> Listing { + // Nothing should need to be imported from outside "env", but let's + // blacklist it just in case. + if self.mod_name != "env" { + Listing::Black + } else { + // Tehcnically we don't have to list blacklisted items here, but we + // do just for clarity. + match self.field_name.as_ref() { + "memory" => Listing::White, + "storage_read" => Listing::White, + "storage_write" => Listing::Black, + "ret" => Listing::White, + "gas" => Listing::White, + "input_length" => Listing::White, + "fetch_input" => Listing::White, + "panic" => Listing::White, + "debug" => Listing::White, + "ccall" => Listing::Black, + "dcall" => Listing::Grey, + "scall" => Listing::White, + "value" => Listing::White, + "create" => Listing::Black, + "suicide" => Listing::White, + "blockhash" => Listing::White, + "blocknumber" => Listing::White, + "coinbase" => Listing::White, + "difficulty" => Listing::White, + "gaslimit" => Listing::White, + "timestamp" => Listing::White, + "address" => Listing::White, + "sender" => Listing::White, + "origin" => Listing::White, + "elog" => Listing::Black, + "extcodesize" => Listing::White, + "extcodecopy" => Listing::White, + "create2" => Listing::Black, + "gasleft" => Listing::White, + _ => Listing::Black, + } + } + } +} diff --git a/kernel-ewasm/validator/src/modules.rs b/kernel-ewasm/validator/src/modules.rs new file mode 100644 index 0000000..1921aee --- /dev/null +++ b/kernel-ewasm/validator/src/modules.rs @@ -0,0 +1,522 @@ +use super::func; +use super::import_entry; +use super::parse_varuint_32; +use super::Cursor; +use super::ImportEntry; +use crate::instructions; +use crate::primitives::CountedList; +use crate::serialization::Deserialize; +#[cfg(not(feature = "std"))] +use pwasm_std::String; +#[cfg(not(feature = "std"))] +use pwasm_std::Vec; +/// A read-only representation of a WASM module. The data is held in WASM binary +/// format in the buffer. All of the functions simply access this buffer. These +/// fields are private as they need initialisation. Currently it only holds +/// references to the sections we care about. +#[derive(Debug, Default)] +pub struct Module<'a> { + /// A reference to the buffer that actually holds the WASM data. + buffer: &'a [u8], + /// The offset into the buffer of the start of the type section + /// (excluding the section type byte). It therefore points to the size + /// of the section. + type_section_offset: Option, + /// The offset into the buffer of the start of the import + /// section (excluding the section type byte). It therefore points to + /// the size of the section. + import_section_offset: Option, + /// The offset into the buffer of the start of the function + /// section (excluding the section type byte). It therefore points to + /// the size of the section. + function_section_offset: Option, + /// The offset into the buffer of the start of the code section + /// (excluding the section type byte). It therefore points to the size + /// of the section. + code_section_offset: Option, + /// The offset into the buffer of the start of the table section + /// (excluding the section type byte). It therefore points to the size + /// of the section. + table_section_offset: Option, +} + +impl<'a> Module<'a> { + /// Create a new `Module` struct using the given buffer. + pub fn new(buffer: &'a [u8]) -> Self { + // Create a cursor, with which we will seek over the WASM code in + // the buffer (self is the buffer, and is read-only). + let mut cursor = Cursor { + current_offset: 0, + body: buffer, + }; + + // The first two steps are to take the magic number and version to + // check that it is valid wasm. This is not strictly necessary, as + // it is the job of the runtime to ensure the wasm is valid (ad we + // rely on that fact), however, it's cheap and allows us prevent + // future versions of wasm code being deployed (for which our + // assumptions may not hold). + + // Take the magic number, check that it matches + if cursor.read_ref_n(4) != &[0, 97, 115, 109] { + panic!("magic number not found"); + } + + // Take the version, check that it matches + if cursor.read_ref_n(4) != &[1, 0, 0, 0] { + panic!("proper version number not found"); + } + + // First we find all of the relevant section offsets. + let mut type_section_offset: Option = None; + let mut import_section_offset: Option = None; + let mut function_section_offset: Option = None; + let mut code_section_offset: Option = None; + let mut table_section_offset: Option = None; + while cursor.current_offset < buffer.len() { + let section: Section = parse_section(&mut cursor); + // There are many section types we don't care about, for + // example, Custom sections generally contain debugging symbols + // and meaningful function names which are irrelevant to the + // current process. We care only about types, imports, + // functions, and code. + match section.type_ { + SectionType::Type => { + if type_section_offset.is_some() { + panic!("multiple type sections"); + } + type_section_offset = Some(section.offset); + } + SectionType::Import => { + if import_section_offset.is_some() { + panic!("multiple import sections"); + } + import_section_offset = Some(section.offset); + } + SectionType::Function => { + if function_section_offset.is_some() { + panic!("multiple function sections"); + } + function_section_offset = Some(section.offset); + } + SectionType::Code => { + if code_section_offset.is_some() { + panic!("multiple code sections"); + } + code_section_offset = Some(section.offset); + } + SectionType::Table => { + if table_section_offset.is_some() { + panic!("multiple code sections"); + } + table_section_offset = Some(section.offset); + } + // We ignore any section we are not interested in. + _ => (), + } + } + if cursor.current_offset != buffer.len() { + panic!("mismatched length"); + } + Module { + buffer, + type_section_offset, + import_section_offset, + function_section_offset, + code_section_offset, + table_section_offset, + } + } + + /// Return an iterator over the imports in the import section. The + /// imports are in order. + pub fn imports(&self) -> Option { + // TODO: generalise to SectionIter + if let Some(imports_offset) = self.import_section_offset { + Some(ImportIterator::new(self.buffer, imports_offset)) + } else { + None + } + } + + /// Return an iterator over the combined function and code sections. + /// Individual access to these sections is not currently exposed. + pub fn functions(&self) -> Option { + // TODO: generalise to SectionIter + if let (Some(functions_offset), Some(code_offset)) = + (self.function_section_offset, self.code_section_offset) + { + Some(FunctionIterator::new( + self.buffer, + functions_offset, + code_offset, + )) + } else { + None + } + } +} + +/// An iterator over the imports in the import section. +pub struct ImportIterator<'a> { + section_offset: usize, + offset_into_section: usize, + buffer: &'a [u8], + n: u32, + current_entry: u32, +} + +impl<'a> ImportIterator<'a> { + fn new(buffer: &'a [u8], section_offset: usize) -> Self { + let mut imports_cursor = Cursor { + current_offset: section_offset, + body: buffer, + }; + // How big is this section in bytes? + let _section_size = parse_varuint_32(&mut imports_cursor); + // How many imports do we have? + let n = parse_varuint_32(&mut imports_cursor); + ImportIterator { + section_offset, + offset_into_section: (imports_cursor.current_offset - section_offset), + buffer, + n, + current_entry: 0, + } + } +} + +impl<'a> Iterator for ImportIterator<'a> { + type Item = ImportEntry; + + fn next(&mut self) -> Option { + if self.current_entry < self.n { + let mut reader = Cursor { + current_offset: self.section_offset + self.offset_into_section, + body: self.buffer, + }; + let val = parse_import(&mut reader); + self.offset_into_section = reader.current_offset - self.section_offset; + self.current_entry += 1; + Some(val) + } else { + None + } + } +} + +// TODO: ugly function +fn parse_import(cursor: &mut Cursor) -> ImportEntry { + let mut reader = Cursor { + current_offset: cursor.current_offset, + body: cursor.body, + }; + let import: import_entry::ImportEntry = + import_entry::ImportEntry::deserialize(&mut reader).expect("counted list"); + let val = ImportEntry { + mod_name: String::from(import.module()), + field_name: String::from(import.field()), + }; + cursor.current_offset = reader.current_offset; + val +} + +/// TODO: this should be made by combining function and code iterators. +/// An iterator over the imports in the import section. +pub struct FunctionIterator<'a> { + function_section_offset: usize, + code_section_offset: usize, + offset_into_function_section: usize, + offset_into_code_section: usize, + buffer: &'a [u8], + n: u32, + current_entry: u32, +} + +impl<'a> FunctionIterator<'a> { + fn new(buffer: &'a [u8], function_section_offset: usize, code_section_offset: usize) -> Self { + let mut functions_cursor = Cursor { + current_offset: function_section_offset, + body: buffer, + }; + let mut code_cursor = Cursor { + current_offset: code_section_offset, + body: buffer, + }; + // Get the sizes of the two sections. These aren't important for + // these, we just need to skip past them. + let _function_section_size = parse_varuint_32(&mut functions_cursor); + let _code_section_size = parse_varuint_32(&mut code_cursor); + let n_functions = parse_varuint_32(&mut functions_cursor); + let n_bodies = parse_varuint_32(&mut code_cursor); + + // These should be the same, if not, our assumptions are invalid or + // the WASM is invalid. In either case we need to abort. + assert_eq!(n_functions, n_bodies); + FunctionIterator { + function_section_offset, + code_section_offset, + offset_into_function_section: (code_cursor.current_offset - code_section_offset), + offset_into_code_section: (functions_cursor.current_offset - function_section_offset), + buffer, + n: n_functions, + current_entry: 0, + } + } +} + +impl<'a> Iterator for FunctionIterator<'a> { + type Item = Function<'a>; + + fn next(&mut self) -> Option { + if self.current_entry < self.n { + let mut functions_cursor = Cursor { + current_offset: self.function_section_offset + self.offset_into_function_section, + body: self.buffer, + }; + let mut code_cursor = Cursor { + current_offset: self.code_section_offset + self.offset_into_code_section, + body: self.buffer, + }; + let val = Function { + function_entry_offset: functions_cursor.current_offset, + code_entry_offset: code_cursor.current_offset, + buffer: self.buffer, + }; + let body_size = parse_varuint_32(&mut code_cursor); + let f_size = parse_varuint_32(&mut functions_cursor); + self.offset_into_function_section = + functions_cursor.current_offset - self.function_section_offset + f_size as usize; + self.offset_into_code_section = + code_cursor.current_offset - self.code_section_offset + body_size as usize; + self.current_entry += 1; + Some(val) + } else { + None + } + } +} + +#[derive(Debug)] +pub struct Function<'a> { + pub function_entry_offset: usize, + pub code_entry_offset: usize, + buffer: &'a [u8], +} + +impl<'a> Function<'a> { + pub fn code(&self) -> Code { + Code::new(self.buffer) + } + + pub fn is_syscall(&self, dcall_i: u32, gasleft_i: u32, sender_i: u32) -> bool { + let mut code_cursor = Cursor { + current_offset: self.code_entry_offset, + body: self.buffer, + }; + let body_size = parse_varuint_32(&mut code_cursor); + let body = &self.buffer + [(code_cursor.current_offset)..(code_cursor.current_offset + body_size as usize)]; + let mut code_iter = Code::new(body); + + // Check that no locals are used + if code_iter.locals.len() > 0 { + return false; + } + + // First we need to check that the instructions are correct, that is: + // 0. call $a + // 1. call $b + // 2. get_local 0 + // 3. get_local 1 + // 4. get_local 2 + // 5. get_local 3 + // 6. call $c + // $a, $b, and $c will be used later. + + // 0. call gasleft + if let Some(instructions::Instruction::Call(f_ind)) = code_iter.next() { + if f_ind != gasleft_i { + return false; + } + } else { + return false; + } + // 1. call sender + if let Some(instructions::Instruction::Call(f_ind)) = code_iter.next() { + if f_ind != sender_i { + return false; + } + } else { + return false; + } + // 2. get_local 0 + if let Some(instructions::Instruction::GetLocal(0)) = code_iter.next() { + } else { + return false; + } + // 3. get_local 1 + if let Some(instructions::Instruction::GetLocal(1)) = code_iter.next() { + } else { + return false; + } + // 4. get_local 2 + if let Some(instructions::Instruction::GetLocal(2)) = code_iter.next() { + } else { + return false; + } + // 5. get_local 3 + if let Some(instructions::Instruction::GetLocal(3)) = code_iter.next() { + } else { + return false; + } + + // 6. call dcall + if let Some(instructions::Instruction::Call(f_ind)) = code_iter.next() { + if f_ind != dcall_i { + return false; + } + } else { + return false; + } + // 7. END + if let Some(instructions::Instruction::End) = code_iter.next() { + } else { + return false; + } + // We have checked locals and code, we don't really care abou the type, so + // we can return true. + true + } + // TODO: we need to account for indirect calls too. + pub fn contains_grey_call(&self, dcall_i: u32) -> bool { + let mut code_cursor = Cursor { + current_offset: self.code_entry_offset, + body: self.buffer, + }; + let body_size = parse_varuint_32(&mut code_cursor); + let body = &self.buffer + [(code_cursor.current_offset)..(code_cursor.current_offset + body_size as usize)]; + let code_iter = Code::new(body); + for instruction in code_iter { + // We only care about Call or CallIndirect instructions + match instruction { + instructions::Instruction::Call(f_ind) => { + // if f_ind is a grey call then we return true, as we are asking the + // question "Does this function contain a call to a greylisted + // import?". + if f_ind == dcall_i { + return true; + } + } + instructions::Instruction::CallIndirect(_type_index, _table_index) => { + // We currently don't have the functionality to check that + // tables are safe. For now we will just forbid indirect + // calls by assuming any indirect call could be a dcall. + return true; + } + _ => {} + } + } + // No instructions were greylisted, so we can return false. + false + } +} + +/// An iterator over the instructions of a function body. +pub struct Code<'a> { + pub locals: Vec, + pub current_offset: usize, + pub body: &'a [u8], +} + +impl<'a> Code<'a> { + pub fn new(body: &'a [u8]) -> Code { + let mut reader = Cursor { + current_offset: 0, + body: body, + }; + let locals: Vec = CountedList::::deserialize(&mut reader) + .expect("counted list") + .into_inner(); + Code { + locals, + current_offset: reader.current_offset, + body: body, + } + } +} + +impl<'a> Iterator for Code<'a> { + type Item = crate::instructions::Instruction; + + fn next(&mut self) -> Option { + if self.current_offset < self.body.len() { + // We need to parse the code into something meaningful + let mut reader = Cursor { + current_offset: self.current_offset, + body: self.body, + }; + let val = Some( + crate::instructions::Instruction::deserialize(&mut reader) + .expect("expected valid instruction"), + ); + self.current_offset = reader.current_offset; + val + } else { + None + } + } +} + +#[derive(Debug)] +enum SectionType { + Custom, + Type, + Import, + Function, + Table, + Memory, + Global, + Export, + Start, + Element, + Code, + Data, +} + +#[derive(Debug)] +struct Section { + type_: SectionType, + // The offset is the byte offset of the start of this + // section, i.e. it points directly to the length byte. + offset: usize, +} + +fn parse_section(cursor: &mut Cursor) -> Section { + let type_n = cursor.read_ref().expect("no bytes left to parse section type"); + let offset = cursor.current_offset; + let size_n = parse_varuint_32(cursor); + let type_ = n_to_section(type_n); + let section = Section { type_, offset }; + cursor.current_offset += size_n as usize; + section +} + +fn n_to_section(byte: &u8) -> SectionType { + match byte { + 0 => SectionType::Custom, + 1 => SectionType::Type, + 2 => SectionType::Import, + 3 => SectionType::Function, + 4 => SectionType::Table, + 5 => SectionType::Memory, + 6 => SectionType::Global, + 7 => SectionType::Export, + 8 => SectionType::Start, + 9 => SectionType::Element, + 10 => SectionType::Code, + 11 => SectionType::Data, + _ => panic!("invalid section type"), + } +} diff --git a/kernel-ewasm/validator/src/primitives.rs b/kernel-ewasm/validator/src/primitives.rs new file mode 100644 index 0000000..27796a1 --- /dev/null +++ b/kernel-ewasm/validator/src/primitives.rs @@ -0,0 +1,712 @@ +// This file is based on parity-wasm from parity, MIT & Apache Licensed +// use crate::rust::{vec::Vec, string::String}; +use crate::{io}; +use crate::{Deserialize}; +use pwasm_std::vec::Vec; +use pwasm_std::String; +use crate::serialization::{Error}; + + +macro_rules! buffered_read { + ($buffer_size: expr, $length: expr, $reader: expr) => { + { + let mut vec_buf = Vec::new(); + let mut total_read = 0; + let mut buf = [0u8; $buffer_size]; + while total_read < $length { + let next_to_read = if $length - total_read > $buffer_size { $buffer_size } else { $length - total_read }; + $reader.read(&mut buf[0..next_to_read])?; + vec_buf.extend_from_slice(&buf[0..next_to_read]); + total_read += next_to_read; + } + vec_buf + } + } +} + + + +/// Unsigned variable-length integer, limited to 32 bits, +/// represented by at most 5 bytes that may contain padding 0x80 bytes. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct VarUint32(u32); + +impl From for usize { + fn from(var: VarUint32) -> usize { + var.0 as usize + } +} + +impl From for u32 { + fn from(var: VarUint32) -> u32 { + var.0 + } +} + +impl From for VarUint32 { + fn from(i: u32) -> VarUint32 { + VarUint32(i) + } +} + +impl From for VarUint32 { + fn from(i: usize) -> VarUint32 { + assert!(i <= u32::max_value() as usize); + VarUint32(i as u32) + } +} + +impl Deserialize for VarUint32 { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let mut res = 0; + let mut shift = 0; + let mut u8buf = [0u8; 1]; + loop { + if shift > 31 { return Err(Error::InvalidVarUint32); } + + reader.read(&mut u8buf)?; + let b = u8buf[0] as u32; + res |= (b & 0x7f).checked_shl(shift).ok_or(Error::InvalidVarUint32)?; + shift += 7; + if (b >> 7) == 0 { + if shift >= 32 && (b as u8).leading_zeros() < 4 { + return Err(Error::InvalidVarInt32); + } + break; + } + } + Ok(VarUint32(res)) + } +} + +/// Unsigned variable-length integer, limited to 64 bits, +/// represented by at most 9 bytes that may contain padding 0x80 bytes. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct VarUint64(u64); + +impl From for u64 { + fn from(var: VarUint64) -> u64 { + var.0 + } +} + +impl Deserialize for VarUint64 { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let mut res = 0; + let mut shift = 0; + let mut u8buf = [0u8; 1]; + loop { + if shift > 63 { return Err(Error::InvalidVarUint64); } + + reader.read(&mut u8buf)?; + let b = u8buf[0] as u64; + res |= (b & 0x7f).checked_shl(shift).ok_or(Error::InvalidVarUint64)?; + shift += 7; + if (b >> 7) == 0 { + if shift >= 64 && (b as u8).leading_zeros() < 7 { + return Err(Error::InvalidVarInt64); + } + break; + } + } + Ok(VarUint64(res)) + } +} + +impl From for VarUint64 { + fn from(u: u64) -> VarUint64 { + VarUint64(u) + } +} + +/// 7-bit unsigned integer, encoded in LEB128 (always 1 byte length). +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct VarUint7(u8); + +impl From for u8 { + fn from(v: VarUint7) -> u8 { + v.0 + } +} + +impl From for VarUint7 { + fn from(v: u8) -> Self { + VarUint7(v) + } +} + +impl Deserialize for VarUint7 { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let mut u8buf = [0u8; 1]; + reader.read(&mut u8buf)?; + Ok(VarUint7(u8buf[0])) + } +} + +/// 7-bit signed integer, encoded in LEB128 (always 1 byte length) +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct VarInt7(i8); + +impl From for i8 { + fn from(v: VarInt7) -> i8 { + v.0 + } +} + +impl From for VarInt7 { + fn from(v: i8) -> VarInt7 { + VarInt7(v) + } +} + +impl Deserialize for VarInt7 { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let mut u8buf = [0u8; 1]; + reader.read(&mut u8buf)?; + + // check if number is not continued! + if u8buf[0] & 0b1000_0000 != 0 { + return Err(Error::InvalidVarInt7(u8buf[0])); + } + + // expand sign + if u8buf[0] & 0b0100_0000 == 0b0100_0000 { u8buf[0] |= 0b1000_0000 } + + Ok(VarInt7(u8buf[0] as i8)) + } +} + +/// 8-bit unsigned integer, NOT encoded in LEB128; +/// it's just a single byte. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Uint8(u8); + +impl From for u8 { + fn from(v: Uint8) -> u8 { + v.0 + } +} + +impl From for Uint8 { + fn from(v: u8) -> Self { + Uint8(v) + } +} + +impl Deserialize for Uint8 { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let mut u8buf = [0u8; 1]; + reader.read(&mut u8buf)?; + Ok(Uint8(u8buf[0])) + } +} + +/// 32-bit signed integer, encoded in LEB128 (can be 1-5 bytes length). +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct VarInt32(i32); + +impl From for i32 { + fn from(v: VarInt32) -> i32 { + v.0 + } +} + +impl From for VarInt32 { + fn from(v: i32) -> VarInt32 { + VarInt32(v) + } +} + +impl Deserialize for VarInt32 { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let mut res = 0; + let mut shift = 0; + let mut u8buf = [0u8; 1]; + loop { + if shift > 31 { return Err(Error::InvalidVarInt32); } + reader.read(&mut u8buf)?; + let b = u8buf[0]; + + res |= ((b & 0x7f) as i32).checked_shl(shift).ok_or(Error::InvalidVarInt32)?; + + shift += 7; + if (b >> 7) == 0 { + if shift < 32 && b & 0b0100_0000 == 0b0100_0000 { + res |= (1i32 << shift).wrapping_neg(); + } else if shift >= 32 && b & 0b0100_0000 == 0b0100_0000 { + if (!(b | 0b1000_0000)).leading_zeros() < 5 { + return Err(Error::InvalidVarInt32); + } + } else if shift >= 32 && b & 0b0100_0000 == 0 { + if b.leading_zeros() < 5 { + return Err(Error::InvalidVarInt32); + } + } + break; + } + } + Ok(VarInt32(res)) + } +} + +/// 64-bit signed integer, encoded in LEB128 (can be 1-9 bytes length). +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct VarInt64(i64); + +impl From for i64 { + fn from(v: VarInt64) -> i64 { + v.0 + } +} + +impl From for VarInt64 { + fn from(v: i64) -> VarInt64 { + VarInt64(v) + } +} + +impl Deserialize for VarInt64 { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let mut res = 0i64; + let mut shift = 0; + let mut u8buf = [0u8; 1]; + + loop { + if shift > 63 { return Err(Error::InvalidVarInt64); } + reader.read(&mut u8buf)?; + let b = u8buf[0]; + + res |= ((b & 0x7f) as i64).checked_shl(shift).ok_or(Error::InvalidVarInt64)?; + + shift += 7; + if (b >> 7) == 0 { + if shift < 64 && b & 0b0100_0000 == 0b0100_0000 { + res |= (1i64 << shift).wrapping_neg(); + } else if shift >= 64 && b & 0b0100_0000 == 0b0100_0000 { + if (b | 0b1000_0000) as i8 != -1 { + return Err(Error::InvalidVarInt64); + } + } else if shift >= 64 && b != 0 { + return Err(Error::InvalidVarInt64); + } + break; + } + } + Ok(VarInt64(res)) + } +} + +/// 32-bit unsigned integer, encoded in little endian. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Uint32(u32); + +impl Deserialize for Uint32 { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let mut buf = [0u8; 4]; + reader.read(&mut buf)?; + // todo check range + Ok(u32::from_le_bytes(buf).into()) + } +} + +impl From for u32 { + fn from(var: Uint32) -> u32 { + var.0 + } +} + +impl From for Uint32 { + fn from(u: u32) -> Self { Uint32(u) } +} + +/// 64-bit unsigned integer, encoded in little endian. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Uint64(u64); + +impl Deserialize for Uint64 { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let mut buf = [0u8; 8]; + reader.read(&mut buf)?; + // todo check range + Ok(u64::from_le_bytes(buf).into()) + } +} + +impl From for Uint64 { + fn from(u: u64) -> Self { Uint64(u) } +} + +impl From for u64 { + fn from(var: Uint64) -> u64 { + var.0 + } +} + + +/// VarUint1, 1-bit value (0/1). +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct VarUint1(bool); + +impl From for bool { + fn from(v: VarUint1) -> bool { + v.0 + } +} + +impl From for VarUint1 { + fn from(b: bool) -> Self { + VarUint1(b) + } +} + +impl Deserialize for VarUint1 { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let mut u8buf = [0u8; 1]; + reader.read(&mut u8buf)?; + match u8buf[0] { + 0 => Ok(VarUint1(false)), + 1 => Ok(VarUint1(true)), + v @ _ => Err(Error::InvalidVarUint1(v)), + } + } +} + +impl Deserialize for String { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let length = u32::from(VarUint32::deserialize(reader)?) as usize; + if length > 0 { + String::from_utf8(buffered_read!(1024, length, reader)).map_err(|_| Error::NonUtf8String) + } + else { + Ok(String::new()) + } + } +} + +/// List for reading sequence of elements typed `T`, given +/// they are preceded by length (serialized as VarUint32). +#[derive(Debug, Clone)] +pub struct CountedList(Vec); + +impl CountedList { + /// Destroy counted list returing inner vector. + pub fn into_inner(self) -> Vec { self.0 } +} + +impl Deserialize for CountedList where T::Error: From { + type Error = T::Error; + + fn deserialize(reader: &mut R) -> Result { + let count: usize = VarUint32::deserialize(reader)?.into(); + let mut result = Vec::new(); + for _ in 0..count { result.push(T::deserialize(reader)?); } + Ok(CountedList(result)) + } +} + +// #[cfg(test)] +// mod tests { + +// use super::super::{deserialize_buffer, Serialize}; +// use super::{CountedList, VarInt7, VarUint32, VarInt32, VarInt64, VarUint64}; +// use crate::io::Error; + +// fn varuint32_ser_test(val: u32, expected: Vec) { +// let mut buf = Vec::new(); +// let v1: VarUint32 = val.into(); +// v1.serialize(&mut buf).expect("to be serialized ok"); +// assert_eq!(expected, buf); +// } + +// fn varuint32_de_test(dt: Vec, expected: u32) { +// let val: VarUint32 = deserialize_buffer(&dt).expect("buf to be serialized"); +// assert_eq!(expected, val.into()); +// } + +// fn varuint32_serde_test(dt: Vec, val: u32) { +// varuint32_de_test(dt.clone(), val); +// varuint32_ser_test(val, dt); +// } + +// fn varint32_ser_test(val: i32, expected: Vec) { +// let mut buf = Vec::new(); +// let v1: VarInt32 = val.into(); +// v1.serialize(&mut buf).expect("to be serialized ok"); +// assert_eq!(expected, buf); +// } + +// fn varint32_de_test(dt: Vec, expected: i32) { +// let val: VarInt32 = deserialize_buffer(&dt).expect("buf to be serialized"); +// assert_eq!(expected, val.into()); +// } + +// fn varint32_serde_test(dt: Vec, val: i32) { +// varint32_de_test(dt.clone(), val); +// varint32_ser_test(val, dt); +// } + +// fn varuint64_ser_test(val: u64, expected: Vec) { +// let mut buf = Vec::new(); +// let v1: VarUint64 = val.into(); +// v1.serialize(&mut buf).expect("to be serialized ok"); +// assert_eq!(expected, buf); +// } + +// fn varuint64_de_test(dt: Vec, expected: u64) { +// let val: VarUint64 = deserialize_buffer(&dt).expect("buf to be serialized"); +// assert_eq!(expected, val.into()); +// } + +// fn varuint64_serde_test(dt: Vec, val: u64) { +// varuint64_de_test(dt.clone(), val); +// varuint64_ser_test(val, dt); +// } + +// fn varint64_ser_test(val: i64, expected: Vec) { +// let mut buf = Vec::new(); +// let v1: VarInt64 = val.into(); +// v1.serialize(&mut buf).expect("to be serialized ok"); +// assert_eq!(expected, buf); +// } + +// fn varint64_de_test(dt: Vec, expected: i64) { +// let val: VarInt64 = deserialize_buffer(&dt).expect("buf to be serialized"); +// assert_eq!(expected, val.into()); +// } + +// fn varint64_serde_test(dt: Vec, val: i64) { +// varint64_de_test(dt.clone(), val); +// varint64_ser_test(val, dt); +// } + +// #[test] +// fn varuint32_0() { +// varuint32_serde_test(vec![0u8; 1], 0); +// } + +// #[test] +// fn varuint32_1() { +// varuint32_serde_test(vec![1u8; 1], 1); +// } + +// #[test] +// fn varuint32_135() { +// varuint32_serde_test(vec![135u8, 0x01], 135); +// } + +// #[test] +// fn varuint32_8192() { +// varuint32_serde_test(vec![0x80, 0x40], 8192); +// } + +// #[test] +// fn varint32_8192() { +// varint32_serde_test(vec![0x80, 0xc0, 0x00], 8192); +// } + +// #[test] +// fn varint32_neg_8192() { +// varint32_serde_test(vec![0x80, 0x40], -8192); +// } + +// #[test] +// fn varuint64_0() { +// varuint64_serde_test(vec![0u8; 1], 0); +// } + +// #[test] +// fn varuint64_1() { +// varuint64_serde_test(vec![1u8; 1], 1); +// } + +// #[test] +// fn varuint64_135() { +// varuint64_serde_test(vec![135u8, 0x01], 135); +// } + +// #[test] +// fn varuint64_8192() { +// varuint64_serde_test(vec![0x80, 0x40], 8192); +// } + +// #[test] +// fn varint64_8192() { +// varint64_serde_test(vec![0x80, 0xc0, 0x00], 8192); +// } + +// #[test] +// fn varint64_neg_8192() { +// varint64_serde_test(vec![0x80, 0x40], -8192); +// } + +// #[test] +// fn varint64_min() { +// varint64_serde_test( +// vec![0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f], +// -9223372036854775808, +// ); +// } + +// #[test] +// fn varint64_bad_extended() { +// let res = deserialize_buffer::(&[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x6f][..]); +// assert!(res.is_err()); +// } + +// #[test] +// fn varint32_bad_extended() { +// let res = deserialize_buffer::(&[0x80, 0x80, 0x80, 0x80, 0x6f][..]); +// assert!(res.is_err()); +// } + +// #[test] +// fn varint32_bad_extended2() { +// let res = deserialize_buffer::(&[0x80, 0x80, 0x80, 0x80, 0x41][..]); +// assert!(res.is_err()); +// } + +// #[test] +// fn varint64_max() { +// varint64_serde_test( +// vec![0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00], +// 9223372036854775807, +// ); +// } + +// #[test] +// fn varint64_too_long() { +// assert!( +// deserialize_buffer::( +// &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00][..], +// ).is_err() +// ); +// } + +// #[test] +// fn varint32_too_long() { +// assert!( +// deserialize_buffer::( +// &[0xff, 0xff, 0xff, 0xff, 0xff, 0x00][..], +// ).is_err() +// ); +// } + +// #[test] +// fn varuint64_too_long() { +// assert!( +// deserialize_buffer::( +// &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00][..], +// ).is_err() +// ); +// } + +// #[test] +// fn varuint32_too_long() { +// assert!( +// deserialize_buffer::( +// &[0xff, 0xff, 0xff, 0xff, 0xff, 0x00][..], +// ).is_err() +// ); +// } + +// #[test] +// fn varuint32_too_long_trailing() { +// assert!( +// deserialize_buffer::( +// &[0xff, 0xff, 0xff, 0xff, 0x7f][..], +// ).is_err() +// ); +// } + +// #[test] +// fn varuint64_too_long_trailing() { +// assert!( +// deserialize_buffer::( +// &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x04][..], +// ).is_err() +// ); +// } + +// #[test] +// fn varint32_min() { +// varint32_serde_test( +// vec![0x80, 0x80, 0x80, 0x80, 0x78], +// -2147483648, +// ); +// } + +// #[test] +// fn varint7_invalid() { +// match deserialize_buffer::(&[240]) { +// Err(Error::InvalidVarInt7(_)) => {}, +// _ => panic!("Should be invalid varint7 error!") +// } +// } + +// #[test] +// fn varint7_neg() { +// assert_eq!(-0x10i8, deserialize_buffer::(&[0x70]).expect("fail").into()); +// } + +// #[test] +// fn varuint32_too_long_nulled() { +// match deserialize_buffer::( +// &[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x78] +// ) { +// Err(Error::InvalidVarUint32) => {}, +// _ => panic!("Should be invalid varuint32"), +// } +// } + +// #[test] +// fn varint32_max() { +// varint32_serde_test( +// vec![0xff, 0xff, 0xff, 0xff, 0x07], +// 2147483647, +// ); +// } + + +// #[test] +// fn counted_list() { +// let payload = [ +// 133u8, //(128+5), length is 5 +// 0x80, 0x80, 0x80, 0x0, // padding +// 0x01, +// 0x7d, +// 0x05, +// 0x07, +// 0x09, +// ]; + +// let list: CountedList = +// deserialize_buffer(&payload).expect("type_section be deserialized"); + +// let vars = list.into_inner(); +// assert_eq!(5, vars.len()); +// let v3: i8 = (*vars.get(1).unwrap()).into(); +// assert_eq!(-0x03i8, v3); +// } +// } diff --git a/kernel-ewasm/validator/src/serialization.rs b/kernel-ewasm/validator/src/serialization.rs new file mode 100644 index 0000000..5b83076 --- /dev/null +++ b/kernel-ewasm/validator/src/serialization.rs @@ -0,0 +1,216 @@ +use crate::{io}; +use crate::primitives::*; +pub use core::fmt; +use pwasm_std::vec::{Vec}; + +use pwasm_std::String; + +/// Deserialization from serial i/o. +pub trait Deserialize : Sized { + /// Serialization error produced by deserialization routine. + type Error: From; + /// Deserialize type from serial i/o + fn deserialize(reader: &mut R) -> Result; +} + +/// Serialization to serial i/o. Takes self by value to consume less memory +/// (parity-wasm IR is being partially freed by filling the result buffer). +pub trait Serialize { + /// Serialization error produced by serialization routine. + type Error: From; + /// Serialize type to serial i/o + fn serialize(self, writer: &mut W) -> Result<(), Self::Error>; +} + +/// Deserialization/serialization error +#[derive(Debug, Clone)] +pub enum Error { + /// Unexpected end of input. + UnexpectedEof, + /// Invalid magic. + InvalidMagic, + /// Unsupported version. + UnsupportedVersion(u32), + /// Inconsistence between declared and actual length. + InconsistentLength { + /// Expected length of the definition. + expected: usize, + /// Actual length of the definition. + actual: usize + }, + /// Other static error. + Other(&'static str), + /// Other allocated error. + HeapOther(String), + /// Invalid/unknown value type declaration. + UnknownValueType(i8), + /// Invalid/unknown table element type declaration. + UnknownTableElementType(i8), + /// Non-utf8 string. + NonUtf8String, + /// Unknown external kind code. + UnknownExternalKind(u8), + /// Unknown internal kind code. + UnknownInternalKind(u8), + /// Unknown opcode encountered. + UnknownOpcode(u8), + /// Unknown SIMD opcode encountered. + UnknownSimdOpcode(u32), + /// Invalid VarUint1 value. + InvalidVarUint1(u8), + /// Invalid VarInt32 value. + InvalidVarInt32, + /// Invalid VarInt64 value. + InvalidVarInt64, + /// Invalid VarUint32 value. + InvalidVarUint32, + /// Invalid VarUint64 value. + InvalidVarUint64, + /// Inconsistent metadata. + InconsistentMetadata, + /// Invalid section id. + InvalidSectionId(u8), + /// Sections are out of order. + SectionsOutOfOrder, + /// Duplicated sections. + DuplicatedSections(u8), + /// Invalid memory reference (should be 0). + InvalidMemoryReference(u8), + /// Invalid table reference (should be 0). + InvalidTableReference(u8), + /// Invalid value used for flags in limits type. + InvalidLimitsFlags(u8), + /// Unknown function form (should be 0x60). + UnknownFunctionForm(u8), + /// Invalid varint7 (should be in -64..63 range). + InvalidVarInt7(u8), + /// Number of function body entries and signatures does not match. + InconsistentCode, + /// Only flags 0, 1, and 2 are accepted on segments. + InvalidSegmentFlags(u32), + /// Sum of counts of locals is greater than 2^32. + TooManyLocals, + /// Duplicated name subsections. + DuplicatedNameSubsections(u8), + /// Unknown name subsection type. + UnknownNameSubsectionType(u8), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Error::UnexpectedEof => write!(f, "Unexpected end of input"), + Error::InvalidMagic => write!(f, "Invalid magic number at start of file"), + Error::UnsupportedVersion(v) => write!(f, "Unsupported wasm version {}", v), + Error::InconsistentLength { expected, actual } => { + write!(f, "Expected length {}, found {}", expected, actual) + } + Error::Other(msg) => write!(f, "{}", msg), + Error::HeapOther(ref msg) => write!(f, "{}", msg), + Error::UnknownValueType(ty) => write!(f, "Invalid or unknown value type {}", ty), + Error::UnknownTableElementType(ty) => write!(f, "Unknown table element type {}", ty), + Error::NonUtf8String => write!(f, "Non-UTF-8 string"), + Error::UnknownExternalKind(kind) => write!(f, "Unknown external kind {}", kind), + Error::UnknownInternalKind(kind) => write!(f, "Unknown internal kind {}", kind), + Error::UnknownOpcode(opcode) => write!(f, "Unknown opcode {}", opcode), + Error::UnknownSimdOpcode(opcode) => write!(f, "Unknown SIMD opcode {}", opcode), + Error::InvalidVarUint1(val) => write!(f, "Not an unsigned 1-bit integer: {}", val), + Error::InvalidVarInt7(val) => write!(f, "Not a signed 7-bit integer: {}", val), + Error::InvalidVarInt32 => write!(f, "Not a signed 32-bit integer"), + Error::InvalidVarUint32 => write!(f, "Not an unsigned 32-bit integer"), + Error::InvalidVarInt64 => write!(f, "Not a signed 64-bit integer"), + Error::InvalidVarUint64 => write!(f, "Not an unsigned 64-bit integer"), + Error::InconsistentMetadata => write!(f, "Inconsistent metadata"), + Error::InvalidSectionId(ref id) => write!(f, "Invalid section id: {}", id), + Error::SectionsOutOfOrder => write!(f, "Sections out of order"), + Error::DuplicatedSections(ref id) => write!(f, "Duplicated sections ({})", id), + Error::InvalidMemoryReference(ref mem_ref) => write!(f, "Invalid memory reference ({})", mem_ref), + Error::InvalidTableReference(ref table_ref) => write!(f, "Invalid table reference ({})", table_ref), + Error::InvalidLimitsFlags(ref flags) => write!(f, "Invalid limits flags ({})", flags), + Error::UnknownFunctionForm(ref form) => write!(f, "Unknown function form ({})", form), + Error::InconsistentCode => write!(f, "Number of function body entries and signatures does not match"), + Error::InvalidSegmentFlags(n) => write!(f, "Invalid segment flags: {}", n), + Error::TooManyLocals => write!(f, "Too many locals"), + Error::DuplicatedNameSubsections(n) => write!(f, "Duplicated name subsections: {}", n), + Error::UnknownNameSubsectionType(n) => write!(f, "Unknown subsection type: {}", n), + } + } +} + +#[cfg(feature = "std")] +impl ::std::error::Error for Error { + fn description(&self) -> &str { + match *self { + Error::UnexpectedEof => "Unexpected end of input", + Error::InvalidMagic => "Invalid magic number at start of file", + Error::UnsupportedVersion(_) => "Unsupported wasm version", + Error::InconsistentLength { .. } => "Inconsistent length", + Error::Other(msg) => msg, + Error::HeapOther(ref msg) => &msg[..], + Error::UnknownValueType(_) => "Invalid or unknown value type", + Error::UnknownTableElementType(_) => "Unknown table element type", + Error::NonUtf8String => "Non-UTF-8 string", + Error::UnknownExternalKind(_) => "Unknown external kind", + Error::UnknownInternalKind(_) => "Unknown internal kind", + Error::UnknownOpcode(_) => "Unknown opcode", + Error::UnknownSimdOpcode(_) => "Unknown SIMD opcode", + Error::InvalidVarUint1(_) => "Not an unsigned 1-bit integer", + Error::InvalidVarInt32 => "Not a signed 32-bit integer", + Error::InvalidVarInt7(_) => "Not a signed 7-bit integer", + Error::InvalidVarUint32 => "Not an unsigned 32-bit integer", + Error::InvalidVarInt64 => "Not a signed 64-bit integer", + Error::InvalidVarUint64 => "Not an unsigned 64-bit integer", + Error::InconsistentMetadata => "Inconsistent metadata", + Error::InvalidSectionId(_) => "Invalid section id", + Error::SectionsOutOfOrder => "Sections out of order", + Error::DuplicatedSections(_) => "Duplicated section", + Error::InvalidMemoryReference(_) => "Invalid memory reference", + Error::InvalidTableReference(_) => "Invalid table reference", + Error::InvalidLimitsFlags(_) => "Invalid limits flags", + Error::UnknownFunctionForm(_) => "Unknown function form", + Error::InconsistentCode => "Number of function body entries and signatures does not match", + Error::InvalidSegmentFlags(_) => "Invalid segment flags", + Error::TooManyLocals => "Too many locals", + Error::DuplicatedNameSubsections(_) => "Duplicated name subsections", + Error::UnknownNameSubsectionType(_) => "Unknown name subsections type", + } + } +} + +impl From for Error { + fn from(err: io::Error) -> Self { + Error::HeapOther(format!("I/O Error: {:?}", err)) + } +} + +/// Unparsed part of the module/section. +pub struct Unparsed(pub Vec); + +impl Deserialize for Unparsed { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let len = VarUint32::deserialize(reader)?.into(); + let mut vec = vec![0u8; len]; + reader.read(&mut vec[..])?; + Ok(Unparsed(vec)) + } +} + +impl From for Vec { + fn from(u: Unparsed) -> Vec { + u.0 + } +} + +/// Deserialize deserializable type from buffer. +pub fn deserialize_buffer(contents: &[u8]) -> Result { + let mut reader = io::Cursor::new(contents); + let result = T::deserialize(&mut reader)?; + if reader.position() != contents.len() { + // It's a TrailingData, since if there is not enough data then + // UnexpectedEof must have been returned earlier in T::deserialize. + return Err(io::Error::TrailingData.into()) + } + Ok(result) +} diff --git a/kernel-ewasm/validator/src/types.rs b/kernel-ewasm/validator/src/types.rs new file mode 100644 index 0000000..08883fd --- /dev/null +++ b/kernel-ewasm/validator/src/types.rs @@ -0,0 +1,172 @@ +// use crate::rust::{fmt, vec::Vec}; +pub use core::fmt; +use pwasm_std::vec::Vec; +use crate::io; +use super::{Deserialize, VarUint7, VarInt7, CountedList, + VarUint32}; +use crate::serialization::{Error}; + +/// Type definition in types section. Currently can be only of the function type. +#[derive(Debug, Clone, PartialEq, Hash, Eq)] +pub enum Type { + /// Function type. + Function(FunctionType), +} + +impl Deserialize for Type { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + Ok(Type::Function(FunctionType::deserialize(reader)?)) + } +} + +/// Value type. +#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)] +pub enum ValueType { + /// 32-bit signed integer + I32, + /// 64-bit signed integer + I64, + /// 32-bit float + F32, + /// 64-bit float + F64, + /// 128-bit SIMD register + V128, +} + +impl Deserialize for ValueType { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let val = VarInt7::deserialize(reader)?; + + match val.into() { + -0x01 => Ok(ValueType::I32), + -0x02 => Ok(ValueType::I64), + -0x03 => Ok(ValueType::F32), + -0x04 => Ok(ValueType::F64), + -0x05 => Ok(ValueType::V128), + // _ => Err(Error::UnknownValueType(val.into())), + _ => panic!("unknown value type") + } + } +} + +/// Block type which is basically `ValueType` + NoResult (to define blocks that have no return type) +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum BlockType { + /// Value-type specified block type + Value(ValueType), + /// No specified block type + NoResult, +} + +impl Deserialize for BlockType { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let val = VarInt7::deserialize(reader)?; + + match val.into() { + -0x01 => Ok(BlockType::Value(ValueType::I32)), + -0x02 => Ok(BlockType::Value(ValueType::I64)), + -0x03 => Ok(BlockType::Value(ValueType::F32)), + -0x04 => Ok(BlockType::Value(ValueType::F64)), + 0x7b => Ok(BlockType::Value(ValueType::V128)), + -0x40 => Ok(BlockType::NoResult), + // _ => Err(Error::UnknownValueType(val.into())), + _ => panic!("unknown value type") + } + } +} + +/// Function signature type. +#[derive(Debug, Clone, PartialEq, Hash, Eq)] +pub struct FunctionType { + form: u8, + params: Vec, + return_type: Option, +} + +impl Default for FunctionType { + fn default() -> Self { + FunctionType { + form: 0x60, + params: Vec::new(), + return_type: None, + } + } +} + +impl FunctionType { + /// New function type given the signature in-params(`params`) and return type (`return_type`) + pub fn new(params: Vec, return_type: Option) -> Self { + FunctionType { + params: params, + return_type: return_type, + ..Default::default() + } + } + /// Function form (currently only valid value is `0x60`) + pub fn form(&self) -> u8 { self.form } + /// Parameters in the function signature. + pub fn params(&self) -> &[ValueType] { &self.params } + /// Mutable parameters in the function signature. + pub fn params_mut(&mut self) -> &mut Vec { &mut self.params } + /// Return type in the function signature, if any. + pub fn return_type(&self) -> Option { self.return_type } + /// Mutable type in the function signature, if any. + pub fn return_type_mut(&mut self) -> &mut Option { &mut self.return_type } +} + +impl Deserialize for FunctionType { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let form: u8 = VarUint7::deserialize(reader)?.into(); + + if form != 0x60 { + return Err(Error::UnknownFunctionForm(form)); + } + + let params: Vec = CountedList::deserialize(reader)?.into_inner(); + + let return_types: u32 = VarUint32::deserialize(reader)?.into(); + + let return_type = if return_types == 1 { + Some(ValueType::deserialize(reader)?) + } else if return_types == 0 { + None + } else { + return Err(Error::Other("Return types length should be 0 or 1")); + }; + + Ok(FunctionType { + form: form, + params: params, + return_type: return_type, + }) + } +} + +/// Table element type. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum TableElementType { + /// A reference to a function with any signature. + AnyFunc, +} + +impl Deserialize for TableElementType { + type Error = Error; + + fn deserialize(reader: &mut R) -> Result { + let val = VarInt7::deserialize(reader)?; + + match val.into() { + -0x10 => Ok(TableElementType::AnyFunc), + _ => Err(Error::UnknownTableElementType(val.into())), + } + } +} diff --git a/kernel-ewasm/validator/test_files/with_syscall_compliant.wat b/kernel-ewasm/validator/test_files/with_syscall_compliant.wat new file mode 100644 index 0000000..8bad055 --- /dev/null +++ b/kernel-ewasm/validator/test_files/with_syscall_compliant.wat @@ -0,0 +1,35 @@ +;;; This is a compliant contract with a syscall. +(module + (type $t0 (func (param i32 i32))) + (type $t3 (func (param i32 i32 i32 i32) (result i32))) + (type $t4 (func (param i64 i32 i32 i32 i32 i32) (result i32))) + (type $t6 (func (result i64))) + (type $t7 (func (result i32))) + (type $t8 (func (result i32))) + (type $t9 (func)) + (import "env" "dcall" (func $env.dcall (type $t4))) + (import "env" "gasleft" (func $env.gasleft (type $t6))) + (import "env" "sender" (func $env.sender (type $t7))) + (import "env" "input_length" (func $env.input_length (type $t8))) + (import "env" "fetch_input" (func $env.fetch_input (type $t7))) + (import "env" "ret" (func $env.ret (type $t0))) + ;; This is the entry point of the contract + (func $call (type $t9) + i32.const 5 + i32.const 3 + i32.add + drop + unreachable) + ;; This is our system call which we have statically linked in + (func $syscall (type $t3) (param $p0 i32) (param $p1 i32) (param $p2 i32) (param $p3 i32) (result i32) + call $env.gasleft + call $env.sender + get_local $p0 + get_local $p1 + get_local $p2 + get_local $p3 + call $env.dcall) + (table $T0 17 17 anyfunc) + (memory $M0 2) + (global $g0 (mut i32) (i32.const 65536)) + (export "call" (func $call))) diff --git a/kernel-ewasm/validator/test_files/with_syscall_extra_dcall.wat b/kernel-ewasm/validator/test_files/with_syscall_extra_dcall.wat new file mode 100644 index 0000000..cf30786 --- /dev/null +++ b/kernel-ewasm/validator/test_files/with_syscall_extra_dcall.wat @@ -0,0 +1,46 @@ +;;; This is a non-compliant contract with a syscall, but also an extra dcall +;;; that needs to be caught, as it is not in a syscall compliant position. +(module + (type $t0 (func (param i32 i32))) + (type $t3 (func (param i32 i32 i32 i32) (result i32))) + (type $t4 (func (param i64 i32 i32 i32 i32 i32) (result i32))) + (type $t6 (func (result i64))) + (type $t7 (func (result i32))) + (type $t8 (func (result i32))) + (type $t9 (func)) + (import "env" "dcall" (func $env.dcall (type $t4))) + (import "env" "gasleft" (func $env.gasleft (type $t6))) + (import "env" "sender" (func $env.sender (type $t7))) + (import "env" "input_length" (func $env.input_length (type $t8))) + (import "env" "fetch_input" (func $env.fetch_input (type $t7))) + (import "env" "ret" (func $env.ret (type $t0))) + ;; This is the entry point of the contract + (func $call (type $t9) + i32.const 5 + i32.const 3 + i32.add + drop + i64.const 5 + i32.const 6 + i32.const 7 + i32.const 8 + i32.const 9 + i32.const 10 + ;; This is the non-compliant dcall, the preceeding 6 constants are just + ;; dummy inputs to pass validation. + call $env.dcall + drop + unreachable) + ;; This is our system call which we have statically linked in + (func $syscall (type $t3) (param $p0 i32) (param $p1 i32) (param $p2 i32) (param $p3 i32) (result i32) + call $env.gasleft + call $env.sender + get_local $p0 + get_local $p1 + get_local $p2 + get_local $p3 + call $env.dcall) + (table $T0 17 17 anyfunc) + (memory $M0 2) + (global $g0 (mut i32) (i32.const 65536)) + (export "call" (func $call))) diff --git a/kernel-ewasm/validator/test_files/with_syscall_noncompliant.wat b/kernel-ewasm/validator/test_files/with_syscall_noncompliant.wat new file mode 100644 index 0000000..52a3476 --- /dev/null +++ b/kernel-ewasm/validator/test_files/with_syscall_noncompliant.wat @@ -0,0 +1,42 @@ +;;; This is a non-compliant contract with a syscall that is similar to the one +;;; specified but not quite (it has a few instructions added). This is the same +;;; as "with_syscall_compliant.wat" except for those 2 instructions. See the +;;; $syscall function below for details. +(module + (type $t0 (func (param i32 i32))) + (type $t3 (func (param i32 i32 i32 i32) (result i32))) + (type $t4 (func (param i64 i32 i32 i32 i32 i32) (result i32))) + (type $t6 (func (result i64))) + (type $t7 (func (result i32))) + (type $t8 (func (result i32))) + (type $t9 (func)) + (import "env" "dcall" (func $env.dcall (type $t4))) + (import "env" "gasleft" (func $env.gasleft (type $t6))) + (import "env" "sender" (func $env.sender (type $t7))) + (import "env" "input_length" (func $env.input_length (type $t8))) + (import "env" "fetch_input" (func $env.fetch_input (type $t7))) + (import "env" "ret" (func $env.ret (type $t0))) + ;; This is the entry point of the contract + (func $call (type $t9) + i32.const 5 + i32.const 3 + i32.add + drop + unreachable) + ;; This is our system call which we have statically linked in + (func $syscall (type $t3) (param $p0 i32) (param $p1 i32) (param $p2 i32) (param $p3 i32) (result i32) + call $env.gasleft + call $env.sender + get_local $p0 + get_local $p1 + ;; These next two instruction have no effect, but make the syscall + ;; non-compliant + i32.const 5 + drop + get_local $p2 + get_local $p3 + call $env.dcall) + (table $T0 17 17 anyfunc) + (memory $M0 2) + (global $g0 (mut i32) (i32.const 65536)) + (export "call" (func $call))) diff --git a/kernel-ewasm/validator/test_files/with_syscall_noncompliant_locals.wat b/kernel-ewasm/validator/test_files/with_syscall_noncompliant_locals.wat new file mode 100644 index 0000000..6ea40ab --- /dev/null +++ b/kernel-ewasm/validator/test_files/with_syscall_noncompliant_locals.wat @@ -0,0 +1,40 @@ +;;; This is a non-compliant contract with a syscall that is similar to the one +;;; specified but not quite (it has a few instructions added). This is the same +;;; as "with_syscall_compliant.wat" except for those 2 instructions. See the +;;; $syscall function below for details. +(module + (type $t0 (func (param i32 i32))) + (type $t3 (func (param i32 i32 i32 i32) (result i32))) + (type $t4 (func (param i64 i32 i32 i32 i32 i32) (result i32))) + (type $t6 (func (result i64))) + (type $t7 (func (result i32))) + (type $t8 (func (result i32))) + (type $t9 (func)) + (import "env" "dcall" (func $env.dcall (type $t4))) + (import "env" "gasleft" (func $env.gasleft (type $t6))) + (import "env" "sender" (func $env.sender (type $t7))) + (import "env" "input_length" (func $env.input_length (type $t8))) + (import "env" "fetch_input" (func $env.fetch_input (type $t7))) + (import "env" "ret" (func $env.ret (type $t0))) + ;; This is the entry point of the contract + (func $call (type $t9) + i32.const 5 + i32.const 3 + i32.add + drop + unreachable) + ;; This is our system call which we have statically linked in + (func $syscall (type $t3) (param $p0 i32) (param $p1 i32) (param $p2 i32) (param $p3 i32) (result i32) + ;; A syscall should not have locals + (local $l0 i32) + call $env.gasleft + call $env.sender + get_local $p0 + get_local $p1 + get_local $p2 + get_local $p3 + call $env.dcall) + (table $T0 17 17 anyfunc) + (memory $M0 2) + (global $g0 (mut i32) (i32.const 65536)) + (export "call" (func $call))) diff --git a/native_validator/.gitignore b/native_validator/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/native_validator/.gitignore @@ -0,0 +1 @@ +target diff --git a/native_validator/Cargo.toml b/native_validator/Cargo.toml new file mode 100644 index 0000000..8be632d --- /dev/null +++ b/native_validator/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "native_validator" +version = "0.1.0" +authors = ["Jake O'Shannessy "] +edition = "2018" + +[dependencies] +parity-wasm = "0.35" +pwasm-std = "0.13" +pwasm-ethereum = "0.8" +wabt = "0.7.1" +validator = { path = "../kernel-ewasm/validator" } + +# [dev-dependencies.pwasm-test] +# git = "https://github.com/paritytech/pwasm-test" +# default-features = false + +# [features] +# std = ["pwasm-std/std", "pwasm-ethereum/std", "pwasm-test/std"] diff --git a/native_validator/rust-toolchain b/native_validator/rust-toolchain new file mode 100644 index 0000000..cff751d --- /dev/null +++ b/native_validator/rust-toolchain @@ -0,0 +1 @@ +nightly-2019-04-01 diff --git a/native_validator/src/main.rs b/native_validator/src/main.rs new file mode 100644 index 0000000..eb4f4ec --- /dev/null +++ b/native_validator/src/main.rs @@ -0,0 +1,47 @@ +extern crate parity_wasm; + +use parity_wasm::elements::{ImportEntry, Module}; +use parity_wasm::elements::Instruction; +use parity_wasm::elements::{ValueType}; +use std::fs::File; +use std::io::Read; +use validator::*; + +fn main() { + + let mut f = File::open("../kernel-ewasm/example_contract_1/build/example_contract_1.wasm").expect("open file"); + + let mut buffer = Vec::new(); + // read the whole file + f.read_to_end(&mut buffer).expect("read file"); + let module: Module = parity_wasm::deserialize_buffer(buffer.as_slice()).expect("deserialise wasm"); + assert!(module.code_section().is_some()); + + // We have now located the bad imports, but this does not establish if they + // are used. It does not check that it is actually used. For now we will + // assumed that if it is imported it is used. This could cause false + // positives where code imports it but does not use it, however, this is not + // expected to be common as most compilers would optimise that out fairly + // trivially, and it also makes it much easier and cheaper for us. + + let validity = module.validity(); + if validity.validation_errors.len() != 0 { + println!("Module is not valid, the following validation errors were found:"); + for (i, ve) in validity.validation_errors.iter().enumerate() { + println!(" {}: {}", i, show_validation(ve)); + } + } else { + println!("Module is valid"); + } +} + +fn show_validation(ve: &ValidityError) -> String { + match ve { + ValidityError::BlacklistedImport(import) => format!("A blacklisted import ({}.{}) was found", import.module(), import.field()), + ValidityError::UnsafeGreylistedCall { + import, + function_index, + instruction_index, + } => format!("A greylisted import ({}.{}) was called unsafely in function {} at instruction {}", import.module(), import.field(), function_index, instruction_index), + } +} diff --git a/run-parity.bat b/run-parity.bat new file mode 100644 index 0000000..8b5bc10 --- /dev/null +++ b/run-parity.bat @@ -0,0 +1,2 @@ +..\parity-ethereum\target\debug\parity --config dev --chain ./kernel-ewasm/wasm-dev-chain.json db kill +..\parity-ethereum\target\debug\parity --config dev --chain ./kernel-ewasm/wasm-dev-chain.json --jsonrpc-apis=all --ws-apis=all --reseal-min-period 0