├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── deny.toml ├── mk ├── cargo.sh ├── install-build-tools.sh ├── llvm-snapshot.gpg.key └── runner ├── rustfmt.toml ├── src ├── input.rs ├── lib.rs ├── no_panic.rs └── reader.rs └── tests └── tests.rs /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto !eol 2 | *.sln eol=crlf 3 | *.vcxproj eol=crlf 4 | *.vcxproj.filters eol=crlf 5 | *.props eol=crlf 6 | *.bat eol=crlf 7 | *.rc eol=crlf 8 | *.pl linguist-language=Assembly 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | permissions: 3 | contents: read 4 | on: 5 | pull_request: 6 | push: 7 | jobs: 8 | rustfmt: 9 | runs-on: ubuntu-18.04 10 | 11 | steps: 12 | - uses: briansmith/actions-rs-toolchain@v1 13 | with: 14 | toolchain: stable 15 | profile: minimal 16 | components: rustfmt 17 | - uses: briansmith/actions-checkout@v2 18 | with: 19 | persist-credentials: false 20 | - run: cargo fmt --all -- --check 21 | 22 | clippy: 23 | runs-on: ubuntu-18.04 24 | 25 | steps: 26 | - uses: briansmith/actions-rs-toolchain@v1 27 | with: 28 | toolchain: stable 29 | profile: minimal 30 | components: clippy 31 | 32 | - uses: briansmith/actions-checkout@v2 33 | with: 34 | persist-credentials: false 35 | 36 | - run: cargo clippy --all-features ---all-targets -- --deny warnings 37 | 38 | audit: 39 | runs-on: ubuntu-18.04 40 | 41 | steps: 42 | - uses: briansmith/actions-rs-toolchain@v1 43 | with: 44 | toolchain: stable 45 | profile: minimal 46 | 47 | - uses: briansmith/actions-cache@v2 48 | with: 49 | path: | 50 | ~/.cargo/bin/cargo-audit 51 | ~/.cargo/.crates.toml 52 | ~/.cargo/.crates2.json 53 | key: ${{ runner.os }}-v2-cargo-audit-0.13.1 54 | 55 | - run: cargo install cargo-audit --vers "0.13.1" 56 | 57 | - uses: briansmith/actions-checkout@v2 58 | with: 59 | persist-credentials: false 60 | 61 | - run: cargo generate-lockfile 62 | 63 | - run: cargo audit --deny warnings 64 | 65 | deny: 66 | runs-on: ubuntu-18.04 67 | 68 | steps: 69 | - uses: briansmith/actions-rs-toolchain@v1 70 | with: 71 | toolchain: stable 72 | profile: minimal 73 | 74 | - uses: briansmith/actions-cache@v2 75 | with: 76 | path: | 77 | ~/.cargo/bin/cargo-deny 78 | ~/.cargo/.crates.toml 79 | ~/.cargo/.crates2.json 80 | key: ${{ runner.os }}-v2-cargo-deny-0.8.4 81 | 82 | - run: cargo install cargo-deny --vers "0.8.4" 83 | 84 | - uses: briansmith/actions-checkout@v2 85 | with: 86 | persist-credentials: false 87 | 88 | - run: cargo deny check 89 | 90 | # Verify that documentation builds. 91 | rustdoc: 92 | runs-on: ubuntu-18.04 93 | 94 | strategy: 95 | matrix: 96 | rust_channel: 97 | - stable 98 | - beta 99 | - nightly 100 | 101 | include: 102 | - target: x86_64-unknown-linux-gnu 103 | 104 | steps: 105 | - uses: briansmith/actions-rs-toolchain@v1 106 | with: 107 | override: true 108 | target: ${{ matrix.target }} 109 | toolchain: ${{ matrix.rust_channel }} 110 | 111 | - uses: briansmith/actions-checkout@v2 112 | with: 113 | persist-credentials: false 114 | 115 | - run: | 116 | cargo doc --all-features 117 | 118 | test: 119 | runs-on: ${{ matrix.host_os }} 120 | 121 | strategy: 122 | matrix: 123 | features: 124 | - # Default 125 | 126 | target: 127 | - i686-unknown-linux-musl 128 | - x86_64-unknown-linux-gnu 129 | 130 | mode: 131 | - # debug 132 | - --release 133 | 134 | rust_channel: 135 | - stable 136 | - nightly 137 | - 1.49.0 # MSRV 138 | - beta 139 | 140 | include: 141 | - target: i686-unknown-linux-musl 142 | host_os: ubuntu-18.04 143 | 144 | - target: x86_64-unknown-linux-gnu 145 | host_os: ubuntu-18.04 146 | 147 | steps: 148 | - uses: briansmith/actions-checkout@v2 149 | with: 150 | persist-credentials: false 151 | 152 | - uses: briansmith/actions-rs-toolchain@v1 153 | with: 154 | override: true 155 | target: ${{ matrix.target }} 156 | toolchain: ${{ matrix.rust_channel }} 157 | 158 | - run: | 159 | cargo test -vv --target=${{ matrix.target }} ${{ matrix.cargo_options }} ${{ matrix.features }} ${{ matrix.mode }} 160 | 161 | coverage: 162 | runs-on: ${{ matrix.host_os }} 163 | 164 | strategy: 165 | matrix: 166 | features: 167 | - --all-features 168 | 169 | target: 170 | - x86_64-unknown-linux-musl 171 | 172 | mode: 173 | - # debug 174 | 175 | # Coverage collection is Nightly-only 176 | rust_channel: 177 | - nightly 178 | 179 | # TODO: targets 180 | include: 181 | - target: x86_64-unknown-linux-musl 182 | host_os: ubuntu-18.04 183 | 184 | steps: 185 | - if: ${{ contains(matrix.host_os, 'ubuntu') }} 186 | run: sudo apt-get update -y 187 | 188 | - uses: briansmith/actions-checkout@v2 189 | with: 190 | persist-credentials: false 191 | 192 | - if: ${{ !contains(matrix.host_os, 'windows') }} 193 | run: RING_COVERAGE=1 mk/install-build-tools.sh --target=${{ matrix.target }} ${{ matrix.features }} 194 | 195 | - uses: briansmith/actions-rs-toolchain@v1 196 | with: 197 | override: true 198 | target: ${{ matrix.target }} 199 | toolchain: ${{ matrix.rust_channel }} 200 | 201 | - if: ${{ matrix.target == 'aarch64-apple-darwin' }} 202 | run: echo "DEVELOPER_DIR=/Applications/Xcode_12.2.app/Contents/Developer" >> $GITHUB_ENV 203 | 204 | - if: ${{ !contains(matrix.host_os, 'windows') }} 205 | run: | 206 | RING_COVERAGE=1 mk/cargo.sh +${{ matrix.rust_channel }} test -vv --target=${{ matrix.target }} ${{ matrix.cargo_options }} ${{ matrix.features }} ${{ matrix.mode }} 207 | 208 | - uses: briansmith/codecov-codecov-action@v1 209 | with: 210 | directory: ./target/${{ matrix.target }}/debug/coverage/reports 211 | fail_ci_if_error: true 212 | verbose: true 213 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | ssl/test/runner/runner 3 | *.bk 4 | *.orig 5 | *.swp 6 | *.swo 7 | doc/*.html 8 | doc/doc.css 9 | *~ 10 | 11 | # Visual Studio Junk 12 | .vs/ 13 | *.opensdf 14 | *.psess 15 | *.sdf 16 | *.sln.docstates 17 | *.suo 18 | *.user 19 | *.userosscache 20 | *.VC.db 21 | *.VC.opendb 22 | *.vsp 23 | *.vspx 24 | *.rsproj 25 | *.sln 26 | *.vcxproj 27 | *.filters 28 | 29 | 30 | # Cargo Junk 31 | Cargo.lock 32 | target/ 33 | 34 | # JetBrains Junk 35 | .idea 36 | *.iml 37 | CMakeLists.txt 38 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Brian Smith "] 3 | description = "Safe, fast, zero-panic, zero-crashing, zero-allocation parsing of untrusted inputs in Rust." 4 | documentation = "https://briansmith.org/rustdoc/untrusted/" 5 | edition = "2018" 6 | license = "ISC" 7 | name = "untrusted" 8 | readme = "README.md" 9 | repository = "https://github.com/briansmith/untrusted" 10 | version = "0.9.0" 11 | 12 | [lib] 13 | name = "untrusted" 14 | 15 | [profile.bench] 16 | opt-level = 2 17 | lto = true 18 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2016 Brian Smith. 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES WITH 2 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 3 | AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, DIRECT, 4 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 5 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 6 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 7 | PERFORMANCE OF THIS SOFTWARE. 8 | 9 | 10 | 11 | untrusted.rs 12 | ============ 13 | 14 | Safe, fast, zero-panic, zero-crashing, zero-allocation parsing of untrusted 15 | inputs in Rust. 16 | 17 | untrusted.rs is 100% Rust with no use of `unsafe`. It never uses the heap. 18 | No part of untrusted.rs's API will ever panic or cause a crash. It is 19 | `#![no_std]` and so it works perfectly with both libcore- and libstd- based 20 | projects. It does not depend on any crates other than libcore. 21 | 22 | untrusted.rs is intended to be used with the latest version of Rust Stable. 23 | It should usually work with the latest Rust Beta and Rust Nightly versions 24 | too. Using a version of untrusted.rs other than the latest release available 25 | on crates.io is not recommended. 26 | 27 | 28 | 29 | Documentation 30 | ------------- 31 | 32 | See the documentation at 33 | https://briansmith.org/rustdoc/untrusted/. 34 | 35 | To use untrusted.rs in your project, add a dependency to your 36 | Cargo.toml like this: 37 | 38 | ``` 39 | [dependencies] 40 | untrusted = "0.2" 41 | ``` 42 | 43 | 44 | 45 | Examples 46 | -------- 47 | 48 | [*ring*](https://github.com/briansmith/ring)'s parser for the subset of ASN.1 49 | DER it needs to understand, 50 | [`ring::der`](https://github.com/briansmith/ring/blob/master/src/der.rs), is 51 | built on top of untrusted.rs. *ring* also uses untrusted.rs to parse ECC public 52 | keys, RSA PKCS#1 1.5 padding, and everything else. 53 | 54 | All of [webpki](https://github.com/briansmith/webpki)'s parsing of X.509 55 | certificates (also ASN.1 DER) is done using untrusted.rs. 56 | 57 | 58 | 59 | Contributing 60 | ------------ 61 | 62 | Patches welcome! 63 | 64 | When contributing changes, state that you agree to license your contribution 65 | under the same terms as the existing code by putting this at the bottom of your 66 | commit message: 67 | 68 | ``` 69 | 70 | I agree to license my contributions to each file under the terms given 71 | at the top of each file I changed. 72 | ``` 73 | 74 | Currently, the biggest needs for this library are: 75 | 76 | * Unit tests. 77 | * Documentation. 78 | * More examples. 79 | * Static analysis and fuzzing. 80 | 81 | 82 | 83 | Online Automated Testing 84 | ------------------------ 85 | 86 | Travis CI is used for Android, Linux, and Mac OS X. The tests are run for the 87 | current release of each Rust channel (Stable, Beta, Nightly). Since 88 | untrusted.rs only depends on libcore and it only uses 100% cross-platform code 89 | without using `unsafe`, it should work anywhere as long as these platforms are 90 | passing. 91 | 92 | 93 | 94 | 95 | 96 | Bug Reporting 97 | ------------- 98 | 99 | Please report bugs either as pull requests or as issues in [the issue 100 | tracker](https://github.com/briansmith/untrusted/issues). untrusted.rs has a 101 | **full disclosure** vulnerability policy. **Please do NOT attempt to report 102 | any security vulnerability in this code privately to anybody.** 103 | 104 | 105 | 106 | License 107 | ------- 108 | 109 | See [LICENSE.txt](LICENSE.txt), an ISC-style (simplified MIT) license. 110 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [advisories] 2 | unmaintained = "deny" 3 | yanked = "deny" 4 | notice = "deny" 5 | 6 | [licenses] 7 | allow = [ 8 | "ISC", 9 | ] 10 | confidence-threshold = 1.0 11 | 12 | [bans] 13 | # We don't maintain a fixed Cargo.lock so enforcing 14 | # `multiple-versions = "deny"` is impractical. 15 | multiple-versions = "allow" 16 | wildcards = "deny" 17 | 18 | [sources] 19 | unknown-registry = "deny" 20 | unknown-git = "deny" 21 | -------------------------------------------------------------------------------- /mk/cargo.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Copyright 2020 Brian Smith. 4 | # 5 | # Permission to use, copy, modify, and/or distribute this software for any 6 | # purpose with or without fee is hereby granted, provided that the above 7 | # copyright notice and this permission notice appear in all copies. 8 | # 9 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 10 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY 12 | # SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 14 | # OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 15 | # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | 17 | set -eux -o pipefail 18 | IFS=$'\n\t' 19 | 20 | rustflags_self_contained="-Clink-self-contained=yes -Clinker=rust-lld" 21 | qemu_aarch64="qemu-aarch64 -L /usr/aarch64-linux-gnu" 22 | qemu_arm="qemu-arm -L /usr/arm-linux-gnueabihf" 23 | 24 | # Avoid putting the Android tools in `$PATH` because there are tools in this 25 | # directory like `clang` that would conflict with the same-named tools that may 26 | # be needed to compile the build script, or to compile for other targets. 27 | if [ -n "${ANDROID_SDK_ROOT-}" ]; then 28 | android_tools=$ANDROID_SDK_ROOT/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin 29 | fi 30 | 31 | for arg in $*; do 32 | case $arg in 33 | --target=*) 34 | target=${arg#*=} 35 | ;; 36 | *) 37 | ;; 38 | esac 39 | done 40 | 41 | # See comments in install-build-tools.sh. 42 | llvm_version=10 43 | if [ -n "${RING_COVERAGE-}" ]; then 44 | llvm_version=11 45 | fi 46 | 47 | case $target in 48 | aarch64-linux-android) 49 | export CC_aarch64_linux_android=$android_tools/aarch64-linux-android21-clang 50 | export AR_aarch64_linux_android=$android_tools/aarch64-linux-android-ar 51 | export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=$android_tools/aarch64-linux-android21-clang 52 | ;; 53 | aarch64-unknown-linux-gnu) 54 | export CC_aarch64_unknown_linux_gnu=clang-$llvm_version 55 | export AR_aarch64_unknown_linux_gnu=llvm-ar-$llvm_version 56 | export CFLAGS_aarch64_unknown_linux_gnu="--sysroot=/usr/aarch64-linux-gnu" 57 | export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc 58 | export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUNNER="$qemu_aarch64" 59 | ;; 60 | aarch64-unknown-linux-musl) 61 | export CC_aarch64_unknown_linux_musl=clang-$llvm_version 62 | export AR_aarch64_unknown_linux_musl=llvm-ar-$llvm_version 63 | export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="$rustflags_self_contained" 64 | export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUNNER="$qemu_aarch64" 65 | ;; 66 | arm-unknown-linux-gnueabihf) 67 | export CC_arm_unknown_linux_gnueabihf=arm-linux-gnueabihf-gcc 68 | export AR_arm_unknown_linux_gnueabihf=arm-linux-gnueabihf-gcc-ar 69 | export CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc 70 | export CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_RUNNER="$qemu_arm" 71 | ;; 72 | armv7-linux-androideabi) 73 | export CC_armv7_linux_androideabi=$android_tools/armv7a-linux-androideabi18-clang 74 | export AR_armv7_linux_androideabi=$android_tools/arm-linux-androideabi-ar 75 | export CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER=$android_tools/armv7a-linux-androideabi18-clang 76 | ;; 77 | armv7-unknown-linux-musleabihf) 78 | export CC_armv7_unknown_linux_musleabihf=clang-$llvm_version 79 | export AR_armv7_unknown_linux_musleabihf=llvm-ar-$llvm_version 80 | export CARGO_TARGET_ARMV7_UNKNOWN_LINUX_MUSLEABIHF_RUSTFLAGS="$rustflags_self_contained" 81 | export CARGO_TARGET_ARMV7_UNKNOWN_LINUX_MUSLEABIHF_RUNNER="$qemu_arm" 82 | ;; 83 | i686-unknown-linux-gnu) 84 | export CC_i686_unknown_linux_gnu=clang-$llvm_version 85 | export AR_i686_unknown_linux_gnu=llvm-ar-$llvm_version 86 | export CARGO_TARGET_I686_UNKNOWN_LINUX_GNU_LINKER=clang-$llvm_version 87 | ;; 88 | i686-unknown-linux-musl) 89 | export CC_i686_unknown_linux_musl=clang-$llvm_version 90 | export AR_i686_unknown_linux_musl=llvm-ar-$llvm_version 91 | export CARGO_TARGET_I686_UNKNOWN_LINUX_MUSL_RUSTFLAGS="$rustflags_self_contained" 92 | ;; 93 | x86_64-unknown-linux-musl) 94 | export CC_x86_64_unknown_linux_musl=clang-$llvm_version 95 | export AR_x86_64_unknown_linux_musl=llvm-ar-$llvm_version 96 | # XXX: Work around https://github.com/rust-lang/rust/issues/79555. 97 | if [ -n "${RING_COVERAGE-}" ]; then 98 | export CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=clang-$llvm_version 99 | else 100 | export CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="$rustflags_self_contained" 101 | fi 102 | ;; 103 | wasm32-unknown-unknown) 104 | # The first two are only needed for when the "wasm_c" feature is enabled. 105 | export CC_wasm32_unknown_unknown=clang-$llvm_version 106 | export AR_wasm32_unknown_unknown=llvm-ar-$llvm_version 107 | export CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER=wasm-bindgen-test-runner 108 | ;; 109 | *) 110 | ;; 111 | esac 112 | 113 | if [ -n "${RING_COVERAGE-}" ]; then 114 | # XXX: Collides between release and debug. 115 | coverage_dir=$PWD/target/$target/debug/coverage 116 | mkdir -p "$coverage_dir" 117 | rm -f "$coverage_dir/*.profraw" 118 | 119 | export RING_BUILD_EXECUTABLE_LIST="$coverage_dir/executables" 120 | truncate --size=0 "$RING_BUILD_EXECUTABLE_LIST" 121 | 122 | # This doesn't work when profiling under QEMU. Instead mk/runner does 123 | # something similar but different. 124 | # export LLVM_PROFILE_FILE="$coverage_dir/%m.profraw" 125 | 126 | # ${target} with hyphens replaced by underscores, lowercase and uppercase. 127 | target_lower=${target//-/_} 128 | target_upper=${target_lower^^} 129 | 130 | cflags_var=CFLAGS_${target_lower} 131 | declare -x "${cflags_var}=-fprofile-instr-generate -fcoverage-mapping ${!cflags_var-}" 132 | 133 | runner_var=CARGO_TARGET_${target_upper}_RUNNER 134 | declare -x "${runner_var}=mk/runner ${!runner_var-}" 135 | 136 | rustflags_var=CARGO_TARGET_${target_upper}_RUSTFLAGS 137 | declare -x "${rustflags_var}=-Zinstrument-coverage ${!rustflags_var-}" 138 | fi 139 | 140 | cargo "$@" 141 | 142 | if [ -n "${RING_COVERAGE-}" ]; then 143 | while read executable; do 144 | basename=$(basename "$executable") 145 | llvm-profdata-$llvm_version merge -sparse ""$coverage_dir"/$basename.profraw" -o "$coverage_dir"/$basename.profdata 146 | mkdir -p "$coverage_dir"/reports 147 | llvm-cov-$llvm_version export \ 148 | --instr-profile "$coverage_dir"/$basename.profdata \ 149 | --format lcov \ 150 | "$executable" \ 151 | > "$coverage_dir"/reports/coverage-$basename.txt 152 | done < "$RING_BUILD_EXECUTABLE_LIST" 153 | fi 154 | -------------------------------------------------------------------------------- /mk/install-build-tools.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Copyright 2020 Brian Smith. 4 | # 5 | # Permission to use, copy, modify, and/or distribute this software for any 6 | # purpose with or without fee is hereby granted, provided that the above 7 | # copyright notice and this permission notice appear in all copies. 8 | # 9 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 10 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY 12 | # SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 14 | # OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 15 | # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | 17 | set -eux -o pipefail 18 | IFS=$'\n\t' 19 | 20 | target=$1 21 | features=${2-} 22 | 23 | function install_packages { 24 | sudo apt-get -yq --no-install-suggests --no-install-recommends install "$@" 25 | } 26 | 27 | use_clang= 28 | case $target in 29 | --target*android*) 30 | mkdir -p "${ANDROID_SDK_ROOT}/licenses" 31 | android_license_file="${ANDROID_SDK_ROOT}/licenses/android-sdk-license" 32 | accept_android_license=24333f8a63b6825ea9c5514f83c2829b004d1fee 33 | grep --quiet --no-messages "$accept_android_license" "$android_license_file" \ 34 | || echo $accept_android_license >> "$android_license_file" 35 | sudo "${ANDROID_SDK_ROOT}/tools/bin/sdkmanager" ndk-bundle 36 | ;; 37 | esac 38 | 39 | case $target in 40 | --target=aarch64-unknown-linux-gnu) 41 | # Clang is needed for code coverage. 42 | use_clang=1 43 | install_packages \ 44 | qemu-user \ 45 | gcc-aarch64-linux-gnu \ 46 | libc6-dev-arm64-cross 47 | ;; 48 | --target=aarch64-unknown-linux-musl|--target=armv7-unknown-linux-musleabihf) 49 | use_clang=1 50 | install_packages \ 51 | qemu-user 52 | ;; 53 | --target=arm-unknown-linux-gnueabihf) 54 | install_packages \ 55 | qemu-user \ 56 | gcc-arm-linux-gnueabihf \ 57 | libc6-dev-armhf-cross 58 | ;; 59 | --target=i686-unknown-linux-gnu) 60 | use_clang=1 61 | install_packages \ 62 | gcc-multilib \ 63 | libc6-dev-i386 64 | ;; 65 | --target=i686-unknown-linux-musl|--target=x86_64-unknown-linux-musl) 66 | use_clang=1 67 | ;; 68 | --target=wasm32-unknown-unknown) 69 | # The version of wasm-bindgen-cli must match the wasm-bindgen version. 70 | wasm_bindgen_version=$(cargo metadata --format-version 1 | jq -r '.packages | map(select( .name == "wasm-bindgen")) | map(.version) | .[0]') 71 | cargo install wasm-bindgen-cli --vers "$wasm_bindgen_version" --bin wasm-bindgen-test-runner 72 | case ${features-} in 73 | *wasm32_c*) 74 | use_clang=1 75 | ;; 76 | *) 77 | ;; 78 | esac 79 | ;; 80 | --target=*) 81 | ;; 82 | esac 83 | 84 | if [ -n "$use_clang" ]; then 85 | llvm_version=10 86 | if [ -n "${RING_COVERAGE-}" ]; then 87 | # https://github.com/rust-lang/rust/pull/79365 upgraded the coverage file 88 | # format to one that only LLVM 11+ can use 89 | llvm_version=11 90 | sudo apt-key add mk/llvm-snapshot.gpg.key 91 | sudo add-apt-repository "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-$llvm_version main" 92 | sudo apt-get update 93 | fi 94 | install_packages clang-$llvm_version llvm-$llvm_version 95 | fi 96 | -------------------------------------------------------------------------------- /mk/llvm-snapshot.gpg.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | Version: GnuPG v1.4.12 (GNU/Linux) 3 | Comment: See https://apt.llvm.org/. 4 | Comment: Fingerprint: 6084 F3CF 814B 57C1 CF12 EFD5 15CF 4D18 AF4F 7421 5 | 6 | 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 52 | bXb5c6gCHESH5PXwPU4jQEE7Ib9J6sbk7ZT2Mw== 53 | =j+4q 54 | -----END PGP PUBLIC KEY BLOCK----- 55 | -------------------------------------------------------------------------------- /mk/runner: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eux -o pipefail 3 | IFS=$'\n\t' 4 | 5 | for arg in $*; do 6 | # There can be some arguments prefixed in front of the executable, e.g. 7 | # when qemu-user is used. There can be arguments after the executable, 8 | # e.g. `cargo test` arguments like `TESTNAME`. 9 | if [[ $arg = */deps/* ]]; then 10 | executable=$arg 11 | break 12 | fi 13 | done 14 | 15 | export LLVM_PROFILE_FILE=$(dirname "$RING_BUILD_EXECUTABLE_LIST")/$(basename "$executable").profraw 16 | 17 | if [ -n "$RING_BUILD_EXECUTABLE_LIST" ]; then 18 | echo "$executable" >> "$RING_BUILD_EXECUTABLE_LIST" 19 | fi 20 | 21 | $* 22 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2018" 2 | max_width = 100 3 | newline_style = "Unix" 4 | reorder_imports = true 5 | use_field_init_shorthand = true 6 | -------------------------------------------------------------------------------- /src/input.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2021 Brian Smith. 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | use crate::{no_panic, Reader}; 16 | 17 | /// A wrapper around `&'a [u8]` that helps in writing panic-free code. 18 | /// 19 | /// No methods of `Input` will ever panic. 20 | /// 21 | /// Intentionally avoids implementing `PartialEq` and `Eq` to avoid implicit 22 | /// non-constant-time comparisons. 23 | #[derive(Clone, Copy)] 24 | pub struct Input<'a> { 25 | value: no_panic::Slice<'a>, 26 | } 27 | 28 | /// The value is intentionally omitted from the output to avoid leaking 29 | /// secrets. 30 | impl core::fmt::Debug for Input<'_> { 31 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 32 | f.debug_struct("Input").finish() 33 | } 34 | } 35 | 36 | impl<'a> Input<'a> { 37 | /// Construct a new `Input` for the given input `bytes`. 38 | pub const fn from(bytes: &'a [u8]) -> Self { 39 | // This limit is important for avoiding integer overflow. In particular, 40 | // `Reader` assumes that an `i + 1 > i` if `input.value.get(i)` does 41 | // not return `None`. According to the Rust language reference, the 42 | // maximum object size is `core::isize::MAX`, and in practice it is 43 | // impossible to create an object of size `core::usize::MAX` or larger. 44 | Self { 45 | value: no_panic::Slice::new(bytes), 46 | } 47 | } 48 | 49 | /// Returns `true` if the input is empty and false otherwise. 50 | #[inline] 51 | pub fn is_empty(&self) -> bool { 52 | self.value.is_empty() 53 | } 54 | 55 | /// Returns the length of the `Input`. 56 | #[inline] 57 | pub fn len(&self) -> usize { 58 | self.value.len() 59 | } 60 | 61 | /// Calls `read` with the given input as a `Reader`, ensuring that `read` 62 | /// consumed the entire input. If `read` does not consume the entire input, 63 | /// `incomplete_read` is returned. 64 | pub fn read_all(&self, incomplete_read: E, read: F) -> Result 65 | where 66 | F: FnOnce(&mut Reader<'a>) -> Result, 67 | { 68 | let mut input = Reader::new(*self); 69 | let result = read(&mut input)?; 70 | if input.at_end() { 71 | Ok(result) 72 | } else { 73 | Err(incomplete_read) 74 | } 75 | } 76 | 77 | /// Access the input as a slice so it can be processed by functions that 78 | /// are not written using the Input/Reader framework. 79 | #[inline] 80 | pub fn as_slice_less_safe(&self) -> &'a [u8] { 81 | self.value.as_slice_less_safe() 82 | } 83 | 84 | pub(super) fn into_value(self) -> no_panic::Slice<'a> { 85 | self.value 86 | } 87 | } 88 | 89 | impl<'a> From<&'a [u8]> for Input<'a> { 90 | #[inline] 91 | fn from(value: &'a [u8]) -> Self { 92 | no_panic::Slice::new(value).into() 93 | } 94 | } 95 | 96 | impl<'a> From> for Input<'a> { 97 | #[inline] 98 | fn from(value: no_panic::Slice<'a>) -> Self { 99 | Self { value } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2021 Brian Smith. 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | //! untrusted.rs: Safe, fast, zero-panic, zero-crashing, zero-allocation 16 | //! parsing of untrusted inputs in Rust. 17 | //! 18 | //! git clone https://github.com/briansmith/untrusted 19 | //! 20 | //! untrusted.rs goes beyond Rust's normal safety guarantees by also 21 | //! guaranteeing that parsing will be panic-free, as long as 22 | //! `untrusted::Input::as_slice_less_safe()` is not used. It avoids copying 23 | //! data and heap allocation and strives to prevent common pitfalls such as 24 | //! accidentally parsing input bytes multiple times. In order to meet these 25 | //! goals, untrusted.rs is limited in functionality such that it works best for 26 | //! input languages with a small fixed amount of lookahead such as ASN.1, TLS, 27 | //! TCP/IP, and many other networking, IPC, and related protocols. Languages 28 | //! that require more lookahead and/or backtracking require some significant 29 | //! contortions to parse using this framework. It would not be realistic to use 30 | //! it for parsing programming language code, for example. 31 | //! 32 | //! The overall pattern for using untrusted.rs is: 33 | //! 34 | //! 1. Write a recursive-descent-style parser for the input language, where the 35 | //! input data is given as a `&mut untrusted::Reader` parameter to each 36 | //! function. Each function should have a return type of `Result` for 37 | //! some value type `V` and some error type `E`, either or both of which may 38 | //! be `()`. Functions for parsing the lowest-level language constructs 39 | //! should be defined. Those lowest-level functions will parse their inputs 40 | //! using `::read_byte()`, `Reader::peek()`, and similar functions. 41 | //! Higher-level language constructs are then parsed by calling the 42 | //! lower-level functions in sequence. 43 | //! 44 | //! 2. Wrap the top-most functions of your recursive-descent parser in 45 | //! functions that take their input data as an `untrusted::Input`. The 46 | //! wrapper functions should call the `Input`'s `read_all` (or a variant 47 | //! thereof) method. The wrapper functions are the only ones that should be 48 | //! exposed outside the parser's module. 49 | //! 50 | //! 3. After receiving the input data to parse, wrap it in an `untrusted::Input` 51 | //! using `untrusted::Input::from()` as early as possible. Pass the 52 | //! `untrusted::Input` to the wrapper functions when they need to be parsed. 53 | //! 54 | //! In general parsers built using `untrusted::Reader` do not need to explicitly 55 | //! check for end-of-input unless they are parsing optional constructs, because 56 | //! `Reader::read_byte()` will return `Err(EndOfInput)` on end-of-input. 57 | //! Similarly, parsers using `untrusted::Reader` generally don't need to check 58 | //! for extra junk at the end of the input as long as the parser's API uses the 59 | //! pattern described above, as `read_all` and its variants automatically check 60 | //! for trailing junk. `Reader::skip_to_end()` must be used when any remaining 61 | //! unread input should be ignored without triggering an error. 62 | //! 63 | //! untrusted.rs works best when all processing of the input data is done 64 | //! through the `untrusted::Input` and `untrusted::Reader` types. In 65 | //! particular, avoid trying to parse input data using functions that take 66 | //! byte slices. However, when you need to access a part of the input data as 67 | //! a slice to use a function that isn't written using untrusted.rs, 68 | //! `Input::as_slice_less_safe()` can be used. 69 | //! 70 | //! It is recommend to use `use untrusted;` and then `untrusted::Input`, 71 | //! `untrusted::Reader`, etc., instead of using `use untrusted::*`. Qualifying 72 | //! the names with `untrusted` helps remind the reader of the code that it is 73 | //! dealing with *untrusted* input. 74 | //! 75 | //! # Examples 76 | //! 77 | //! [*ring*](https://github.com/briansmith/ring)'s parser for the subset of 78 | //! ASN.1 DER it needs to understand, 79 | //! [`ring::der`](https://github.com/briansmith/ring/blob/main/src/io/der.rs), 80 | //! is built on top of untrusted.rs. *ring* also uses untrusted.rs to parse ECC 81 | //! public keys, RSA PKCS#1 1.5 padding, and for all other parsing it does. 82 | //! 83 | //! All of [webpki](https://github.com/briansmith/webpki)'s parsing of X.509 84 | //! certificates (also ASN.1 DER) is done using untrusted.rs. 85 | 86 | #![doc(html_root_url = "https://briansmith.org/rustdoc/")] 87 | #![no_std] 88 | 89 | mod input; 90 | mod no_panic; 91 | mod reader; 92 | 93 | pub use { 94 | input::Input, 95 | reader::{EndOfInput, Reader}, 96 | }; 97 | 98 | /// Calls `read` with the given input as a `Reader`, ensuring that `read` 99 | /// consumed the entire input. When `input` is `None`, `read` will be 100 | /// called with `None`. 101 | pub fn read_all_optional<'a, F, R, E>( 102 | input: Option>, 103 | incomplete_read: E, 104 | read: F, 105 | ) -> Result 106 | where 107 | F: FnOnce(Option<&mut Reader<'a>>) -> Result, 108 | { 109 | match input { 110 | Some(input) => { 111 | let mut input = Reader::new(input); 112 | let result = read(Some(&mut input))?; 113 | if input.at_end() { 114 | Ok(result) 115 | } else { 116 | Err(incomplete_read) 117 | } 118 | } 119 | None => read(None), 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/no_panic.rs: -------------------------------------------------------------------------------- 1 | /// A wrapper around a slice that exposes no functions that can panic. 2 | /// 3 | /// Intentionally avoids implementing `Debug`, `Eq`, and `PartialEq` to avoid 4 | /// creating a side channel that would leak information about the value. 5 | #[derive(Clone, Copy)] 6 | pub struct Slice<'a> { 7 | bytes: &'a [u8], 8 | } 9 | 10 | impl<'a> Slice<'a> { 11 | #[inline] 12 | pub const fn new(bytes: &'a [u8]) -> Self { 13 | Self { bytes } 14 | } 15 | 16 | #[inline] 17 | pub fn get(&self, i: usize) -> Option<&u8> { 18 | self.bytes.get(i) 19 | } 20 | 21 | #[inline] 22 | pub fn subslice(&self, r: core::ops::Range) -> Option { 23 | self.bytes.get(r).map(|bytes| Self { bytes }) 24 | } 25 | 26 | #[inline] 27 | pub fn is_empty(&self) -> bool { 28 | self.bytes.is_empty() 29 | } 30 | 31 | #[inline] 32 | pub fn len(&self) -> usize { 33 | self.bytes.len() 34 | } 35 | 36 | #[inline] 37 | pub fn as_slice_less_safe(&self) -> &'a [u8] { 38 | self.bytes 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/reader.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2021 Brian Smith. 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | use crate::{no_panic, Input}; 16 | 17 | /// A read-only, forward-only cursor into the data in an `Input`. 18 | /// 19 | /// Using `Reader` to parse input helps to ensure that no byte of the input 20 | /// will be accidentally processed more than once. Using `Reader` in 21 | /// conjunction with `read_all` and `read_all_optional` helps ensure that no 22 | /// byte of the input is accidentally left unprocessed. The methods of `Reader` 23 | /// never panic, so `Reader` also assists the writing of panic-free code. 24 | /// 25 | /// Intentionally avoids implementing `PartialEq` and `Eq` to avoid implicit 26 | /// non-constant-time comparisons. 27 | pub struct Reader<'a> { 28 | input: no_panic::Slice<'a>, 29 | i: usize, 30 | } 31 | 32 | /// Avoids writing the value or position to avoid creating a side channel, 33 | /// though `Reader` can't avoid leaking the position via timing. 34 | impl core::fmt::Debug for Reader<'_> { 35 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 36 | f.debug_struct("Reader").finish() 37 | } 38 | } 39 | 40 | impl<'a> Reader<'a> { 41 | /// Construct a new Reader for the given input. Use `read_all` or 42 | /// `read_all_optional` instead of `Reader::new` whenever possible. 43 | #[inline] 44 | pub fn new(input: Input<'a>) -> Self { 45 | Self { 46 | input: input.into_value(), 47 | i: 0, 48 | } 49 | } 50 | 51 | /// Returns `true` if the reader is at the end of the input, and `false` 52 | /// otherwise. 53 | #[inline] 54 | pub fn at_end(&self) -> bool { 55 | self.i == self.input.len() 56 | } 57 | 58 | /// Returns `true` if there is at least one more byte in the input and that 59 | /// byte is equal to `b`, and false otherwise. 60 | #[inline] 61 | pub fn peek(&self, b: u8) -> bool { 62 | match self.input.get(self.i) { 63 | Some(actual_b) => b == *actual_b, 64 | None => false, 65 | } 66 | } 67 | 68 | /// Reads the next input byte. 69 | /// 70 | /// Returns `Ok(b)` where `b` is the next input byte, or `Err(EndOfInput)` 71 | /// if the `Reader` is at the end of the input. 72 | #[inline] 73 | pub fn read_byte(&mut self) -> Result { 74 | match self.input.get(self.i) { 75 | Some(b) => { 76 | self.i += 1; // safe from overflow; see Input::from(). 77 | Ok(*b) 78 | } 79 | None => Err(EndOfInput), 80 | } 81 | } 82 | 83 | /// Skips `num_bytes` of the input, returning the skipped input as an 84 | /// `Input`. 85 | /// 86 | /// Returns `Ok(i)` if there are at least `num_bytes` of input remaining, 87 | /// and `Err(EndOfInput)` otherwise. 88 | #[inline] 89 | pub fn read_bytes(&mut self, num_bytes: usize) -> Result, EndOfInput> { 90 | let new_i = self.i.checked_add(num_bytes).ok_or(EndOfInput)?; 91 | let ret = self 92 | .input 93 | .subslice(self.i..new_i) 94 | .map(From::from) 95 | .ok_or(EndOfInput)?; 96 | self.i = new_i; 97 | Ok(ret) 98 | } 99 | 100 | /// Skips the reader to the end of the input, returning the skipped input 101 | /// as an `Input`. 102 | #[inline] 103 | pub fn read_bytes_to_end(&mut self) -> Input<'a> { 104 | let to_skip = self.input.len() - self.i; 105 | self.read_bytes(to_skip).unwrap() 106 | } 107 | 108 | /// Calls `read()` with the given input as a `Reader`. On success, returns a 109 | /// pair `(bytes_read, r)` where `bytes_read` is what `read()` consumed and 110 | /// `r` is `read()`'s return value. 111 | pub fn read_partial(&mut self, read: F) -> Result<(Input<'a>, R), E> 112 | where 113 | F: FnOnce(&mut Reader<'a>) -> Result, 114 | { 115 | let start = self.i; 116 | let r = read(self)?; 117 | let bytes_read = self.input.subslice(start..self.i).unwrap().into(); 118 | Ok((bytes_read, r)) 119 | } 120 | 121 | /// Skips `num_bytes` of the input. 122 | /// 123 | /// Returns `Ok(i)` if there are at least `num_bytes` of input remaining, 124 | /// and `Err(EndOfInput)` otherwise. 125 | #[inline] 126 | pub fn skip(&mut self, num_bytes: usize) -> Result<(), EndOfInput> { 127 | self.read_bytes(num_bytes).map(|_| ()) 128 | } 129 | 130 | /// Skips the reader to the end of the input. 131 | #[inline] 132 | pub fn skip_to_end(&mut self) { 133 | let _ = self.read_bytes_to_end(); 134 | } 135 | } 136 | 137 | /// The error type used to indicate the end of the input was reached before the 138 | /// operation could be completed. 139 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 140 | pub struct EndOfInput; 141 | -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2019 Brian Smith. 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #[test] 16 | fn test_debug() { 17 | const INPUTS: &[&[u8]] = &[b"", b"foo"]; 18 | for input in INPUTS { 19 | let input = untrusted::Input::from(input); 20 | assert_eq!(format!("{:?}", &input), "Input"); 21 | input 22 | .read_all(untrusted::EndOfInput, |r| { 23 | assert_eq!(format!("{:?}", r), "Reader"); 24 | r.skip_to_end(); 25 | assert_eq!(format!("{:?}", r), "Reader"); 26 | Ok(()) 27 | }) 28 | .unwrap(); 29 | } 30 | } 31 | 32 | #[test] 33 | fn test_input_clone_and_copy() { 34 | const INPUTS: &[&[u8]] = &[b"", b"a", b"foo"]; 35 | for input in INPUTS { 36 | let input = untrusted::Input::from(input); 37 | let copy = input; 38 | assert_eq!(input.as_slice_less_safe(), copy.as_slice_less_safe()); 39 | assert_eq!( 40 | input.as_slice_less_safe(), 41 | input.clone().as_slice_less_safe() 42 | ); 43 | } 44 | } 45 | 46 | #[test] 47 | fn test_input_from() { 48 | let _ = untrusted::Input::from(b"foo"); 49 | } 50 | 51 | #[test] 52 | fn test_input_is_empty() { 53 | let input = untrusted::Input::from(b""); 54 | assert!(input.is_empty()); 55 | let input = untrusted::Input::from(b"foo"); 56 | assert!(!input.is_empty()); 57 | } 58 | 59 | #[test] 60 | fn test_input_len() { 61 | let input = untrusted::Input::from(b"foo"); 62 | assert_eq!(input.len(), 3); 63 | } 64 | 65 | #[test] 66 | fn test_input_read_all() { 67 | let input = untrusted::Input::from(b"foo"); 68 | let result = input.read_all(untrusted::EndOfInput, |input| { 69 | assert_eq!(b'f', input.read_byte()?); 70 | assert_eq!(b'o', input.read_byte()?); 71 | assert_eq!(b'o', input.read_byte()?); 72 | assert!(input.at_end()); 73 | Ok(()) 74 | }); 75 | assert_eq!(result, Ok(())); 76 | } 77 | 78 | #[test] 79 | fn test_input_read_all_unconsume() { 80 | let input = untrusted::Input::from(b"foo"); 81 | let result = input.read_all(untrusted::EndOfInput, |input| { 82 | assert_eq!(b'f', input.read_byte()?); 83 | assert!(!input.at_end()); 84 | Ok(()) 85 | }); 86 | assert_eq!(result, Err(untrusted::EndOfInput)); 87 | } 88 | 89 | #[test] 90 | fn test_input_as_slice_less_safe() { 91 | let slice = b"foo"; 92 | let input = untrusted::Input::from(slice); 93 | assert_eq!(input.as_slice_less_safe(), slice); 94 | } 95 | 96 | #[test] 97 | fn using_reader_after_skip_and_get_error_returns_error_must_not_panic() { 98 | let input = untrusted::Input::from(&[]); 99 | let r = input.read_all(untrusted::EndOfInput, |input| { 100 | let r = input.read_bytes(1); 101 | assert_eq!(r.unwrap_err(), untrusted::EndOfInput); 102 | Ok(input.read_bytes_to_end()) 103 | }); 104 | let _ = r; // "Use" r. The value of `r` is undefined here. 105 | } 106 | 107 | #[test] 108 | fn size_assumptions() { 109 | // Assume that a pointer can address any point in the address space, and 110 | // infer that this implies that a byte slice will never be 111 | // `core::usize::MAX` bytes long. 112 | assert_eq!( 113 | core::mem::size_of::<*const u8>(), 114 | core::mem::size_of::() 115 | ); 116 | } 117 | 118 | #[test] 119 | fn const_fn() { 120 | const _INPUT: untrusted::Input<'static> = untrusted::Input::from(&[]); 121 | } 122 | 123 | #[test] 124 | fn test_vec_into() { 125 | extern crate std; 126 | let vec = vec![0u8; 0]; 127 | let _x: untrusted::Input = (&vec[..]).into(); 128 | } 129 | 130 | #[test] 131 | fn test_from_slice() { 132 | let slice: &[u8] = &[0u8]; 133 | let _x: untrusted::Input = slice.into(); 134 | } 135 | --------------------------------------------------------------------------------