From 68c33519d33335380fd1e1d2e15392c0b7daad2f Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 10 Aug 2022 10:50:30 +1000 Subject: [PATCH 1/5] Remove unneeded extern crate core We have edition 2018 now, we no longer need to explicitly use `extern crate core`. --- src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1154f5a35..bf4a624bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,9 +60,6 @@ assert_eq!(variant, Variant::Bech32); #[cfg(all(not(feature = "std"), not(test)))] extern crate alloc; -#[cfg(any(test, feature = "std"))] -extern crate core; - #[cfg(all(not(feature = "std"), not(test)))] use alloc::{string::String, vec::Vec}; From 995cb7c1cac62a806fb5bb1a6c3a40dc23ce2bbb Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 28 Jul 2022 10:38:14 +1000 Subject: [PATCH 2/5] Move embedded to sub-dir with-allocator In preparation for adding a no-allocator embedded test crate; move the `embedded` crate (which uses a global allocator) to a subdirectory called `with-allocator`. --- .github/workflows/rust.yml | 5 +++-- .gitignore | 4 ++-- embedded/{ => with-allocator}/Cargo.toml | 6 +++--- embedded/{ => with-allocator}/memory.x | 0 embedded/{ => with-allocator}/src/main.rs | 0 5 files changed, 8 insertions(+), 7 deletions(-) rename embedded/{ => with-allocator}/Cargo.toml (81%) rename embedded/{ => with-allocator}/memory.x (100%) rename embedded/{ => with-allocator}/src/main.rs (100%) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 61d803835..9582d6d79 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -64,7 +64,8 @@ jobs: command: clippy args: -- -D warnings - Embedded: + EmbeddedWithAlloc: + name: no_std with alloc runs-on: ubuntu-latest steps: - name: Checkout @@ -83,4 +84,4 @@ jobs: env: RUSTFLAGS: "-C link-arg=-Tlink.x" CARGO_TARGET_THUMBV7M_NONE_EABI_RUNNER: "qemu-system-arm -cpu cortex-m3 -machine mps2-an385 -nographic -semihosting-config enable=on,target=native -kernel" - run: cd embedded && cargo run --target thumbv7m-none-eabi + run: cd embedded/with-allocator && cargo run --target thumbv7m-none-eabi diff --git a/.gitignore b/.gitignore index 15df54398..9aa70d3ef 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,5 @@ *.o /Cargo.lock -/embedded/Cargo.lock -/embedded/.cargo +/embedded/with-allocator/Cargo.lock +/embedded/with-allocator/.cargo diff --git a/embedded/Cargo.toml b/embedded/with-allocator/Cargo.toml similarity index 81% rename from embedded/Cargo.toml rename to embedded/with-allocator/Cargo.toml index d2aaf6079..d4010dc02 100644 --- a/embedded/Cargo.toml +++ b/embedded/with-allocator/Cargo.toml @@ -2,7 +2,7 @@ authors = ["Riccardo Casatta "] edition = "2018" readme = "README.md" -name = "embedded" +name = "with-allocator" version = "0.1.0" [dependencies] @@ -11,10 +11,10 @@ cortex-m-rt = "0.6.10" cortex-m-semihosting = "0.3.3" panic-halt = "0.2.0" alloc-cortex-m = "0.4.1" -bech32 = { path="../", default-features = false } +bech32 = { path="../../", default-features = false } [[bin]] -name = "embedded" +name = "with-allocator" test = false bench = false diff --git a/embedded/memory.x b/embedded/with-allocator/memory.x similarity index 100% rename from embedded/memory.x rename to embedded/with-allocator/memory.x diff --git a/embedded/src/main.rs b/embedded/with-allocator/src/main.rs similarity index 100% rename from embedded/src/main.rs rename to embedded/with-allocator/src/main.rs From a6a37d89abe00ef509b8380605c5f05998ff63e7 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 28 Jul 2022 10:58:00 +1000 Subject: [PATCH 3/5] Add "alloc" feature We would like users to be able to use parts of this library in a `no_std` environment without an allocator. To achieve this add an "alloc" feature and feature gate any code that requires allocation behind "alloc"/"std". Update the CI test job to run the test with each feature on its own. --- .github/workflows/rust.yml | 7 ++++++- Cargo.toml | 4 +++- src/lib.rs | 32 ++++++++++++++++++++++++++++---- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9582d6d79..9cae1f256 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,3 +1,4 @@ + on: [pull_request] name: Continuous Integration @@ -22,7 +23,11 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test - args: --verbose --features strict + args: --verbose --no-default-features --features strict alloc + - uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --no-default-features --features strict std fmt: name: Rustfmt diff --git a/Cargo.toml b/Cargo.toml index d4f2391e1..31daa1ce4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,9 @@ edition = "2018" [features] default = ["std"] -std = [] +std = ["alloc"] +alloc = [] + # Only for CI to make all warnings errors, do not activate otherwise (may break forward compatibility) strict = [] diff --git a/src/lib.rs b/src/lib.rs index bf4a624bc..079114aaa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,18 +54,19 @@ assert_eq!(variant, Variant::Bech32); #![deny(non_camel_case_types)] #![deny(non_snake_case)] #![deny(unused_mut)] + #![cfg_attr(feature = "strict", deny(warnings))] #![cfg_attr(all(not(feature = "std"), not(test)), no_std)] -#[cfg(all(not(feature = "std"), not(test)))] +#[cfg(feature = "alloc")] extern crate alloc; -#[cfg(all(not(feature = "std"), not(test)))] +#[cfg(all(feature = "alloc", not(feature = "std")))] use alloc::{string::String, vec::Vec}; -#[cfg(all(not(feature = "std"), not(test)))] +#[cfg(all(feature = "alloc", not(feature = "std")))] use alloc::borrow::Cow; -#[cfg(any(feature = "std", test))] +#[cfg(feature = "std")] use std::borrow::Cow; use core::{fmt, mem}; @@ -228,6 +229,7 @@ pub trait FromBase32: Sized { fn from_base32(b32: &[u5]) -> Result; } +#[cfg(feature = "alloc")] impl WriteBase32 for Vec { type Err = (); @@ -242,6 +244,7 @@ impl WriteBase32 for Vec { } } +#[cfg(feature = "alloc")] impl FromBase32 for Vec { type Err = Error; @@ -253,6 +256,7 @@ impl FromBase32 for Vec { } /// A trait for converting a value to a type `T` that represents a `u5` slice. +#[cfg(feature = "alloc")] pub trait ToBase32 { /// Convert `Self` to base32 vector fn to_base32(&self) -> Vec { @@ -267,11 +271,13 @@ pub trait ToBase32 { } /// Interface to calculate the length of the base32 representation before actually serializing +#[cfg(feature = "alloc")] pub trait Base32Len: ToBase32 { /// Calculate the base32 serialized length fn base32_len(&self) -> usize; } +#[cfg(feature = "alloc")] impl> ToBase32 for T { fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { // Amount of bits left over from last round, stored in buffer. @@ -316,6 +322,7 @@ impl> ToBase32 for T { } } +#[cfg(feature = "alloc")] impl> Base32Len for T { fn base32_len(&self) -> usize { let bits = self.as_ref().len() * 8; @@ -337,6 +344,7 @@ pub trait CheckBase32> { fn check_base32(self) -> Result; } +#[cfg(feature = "alloc")] impl> CheckBase32> for T { type Err = Error; @@ -349,6 +357,7 @@ impl> CheckBase32> for T { } #[derive(Clone, Copy, PartialEq, Eq)] +#[cfg(feature = "alloc")] enum Case { Upper, Lower, @@ -361,6 +370,7 @@ enum Case { /// * **MixedCase**: If the HRP contains both uppercase and lowercase characters. /// * **InvalidChar**: If the HRP contains any non-ASCII characters (outside 33..=126). /// * **InvalidLength**: If the HRP is outside 1..83 characters long. +#[cfg(feature = "alloc")] fn check_hrp(hrp: &str) -> Result { if hrp.is_empty() || hrp.len() > 83 { return Err(Error::InvalidLength); @@ -400,6 +410,7 @@ fn check_hrp(hrp: &str) -> Result { /// * If [check_hrp] returns an error for the given HRP. /// # Deviations from standard /// * No length limits are enforced for the data part +#[cfg(feature = "alloc")] pub fn encode_to_fmt>( fmt: &mut fmt::Write, hrp: &str, @@ -429,6 +440,7 @@ pub fn encode_to_fmt>( /// * If [check_hrp] returns an error for the given HRP. /// # Deviations from standard /// * No length limits are enforced for the data part +#[cfg(feature = "alloc")] pub fn encode_without_checksum_to_fmt>( fmt: &mut fmt::Write, hrp: &str, @@ -467,6 +479,7 @@ const BECH32M_CONST: u32 = 0x2bc8_30a3; impl Variant { // Produce the variant based on the remainder of the polymod operation + #[cfg(feature = "alloc")] fn from_remainder(c: u32) -> Option { match c { BECH32_CONST => Some(Variant::Bech32), @@ -489,6 +502,7 @@ impl Variant { /// * If [check_hrp] returns an error for the given HRP. /// # Deviations from standard /// * No length limits are enforced for the data part +#[cfg(feature = "alloc")] pub fn encode>(hrp: &str, data: T, variant: Variant) -> Result { let mut buf = String::new(); encode_to_fmt(&mut buf, hrp, data, variant)?.unwrap(); @@ -501,6 +515,7 @@ pub fn encode>(hrp: &str, data: T, variant: Variant) -> Result>(hrp: &str, data: T) -> Result { let mut buf = String::new(); encode_without_checksum_to_fmt(&mut buf, hrp, data)?.unwrap(); @@ -510,6 +525,7 @@ pub fn encode_without_checksum>(hrp: &str, data: T) -> Result Result<(String, Vec, Variant), Error> { let (hrp_lower, mut data) = split_and_decode(s)?; if data.len() < CHECKSUM_LENGTH { @@ -531,11 +547,13 @@ pub fn decode(s: &str) -> Result<(String, Vec, Variant), Error> { /// Decode a bech32 string into the raw HRP and the data bytes, assuming no checksum. /// /// Returns the HRP in lowercase and the data. +#[cfg(feature = "alloc")] pub fn decode_without_checksum(s: &str) -> Result<(String, Vec), Error> { split_and_decode(s) } /// Decode a bech32 string into the raw HRP and the `u5` data. +#[cfg(feature = "alloc")] fn split_and_decode(s: &str) -> Result<(String, Vec), Error> { // Split at separator and check for two pieces let (raw_hrp, raw_data) = match s.rfind(SEP) { @@ -592,12 +610,14 @@ fn split_and_decode(s: &str) -> Result<(String, Vec), Error> { Ok((hrp_lower, data)) } +#[cfg(feature = "alloc")] fn verify_checksum(hrp: &[u8], data: &[u5]) -> Option { let mut exp = hrp_expand(hrp); exp.extend_from_slice(data); Variant::from_remainder(polymod(&exp)) } +#[cfg(feature = "alloc")] fn hrp_expand(hrp: &[u8]) -> Vec { let mut v: Vec = Vec::new(); for b in hrp { @@ -610,6 +630,7 @@ fn hrp_expand(hrp: &[u8]) -> Vec { v } +#[cfg(feature = "alloc")] fn polymod(values: &[u5]) -> u32 { let mut chk: u32 = 1; let mut b: u8; @@ -638,6 +659,7 @@ const CHARSET: [char; 32] = [ ]; /// Reverse character set. Maps ASCII byte -> CHARSET index on [0,31] +#[cfg(feature = "alloc")] const CHARSET_REV: [i8; 128] = [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, @@ -718,6 +740,7 @@ impl std::error::Error for Error { /// let base5 = convert_bits(&[0xff], 8, 5, true); /// assert_eq!(base5.unwrap(), vec![0x1f, 0x1c]); /// ``` +#[cfg(feature = "alloc")] pub fn convert_bits(data: &[T], from: u32, to: u32, pad: bool) -> Result, Error> where T: Into + Copy, @@ -753,6 +776,7 @@ where } #[cfg(test)] +#[cfg(feature = "alloc")] // Note, all the unit tests currently require an allocator. mod tests { use super::*; From d938b91549c73d9a089570e02a10c941738712e2 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 28 Jul 2022 10:59:44 +1000 Subject: [PATCH 4/5] Add no-allocator crate In order to test that `bech32` can be built in a `no_std` environment without an allocator add a crate `no-allocator` to the `embedded` directory. Add a CI job to build the crate. --- .github/workflows/rust.yml | 16 ++++++++++++++++ .gitignore | 2 ++ embedded/no-allocator/Cargo.toml | 15 +++++++++++++++ embedded/no-allocator/README.md | 10 ++++++++++ embedded/no-allocator/src/main.rs | 26 ++++++++++++++++++++++++++ 5 files changed, 69 insertions(+) create mode 100644 embedded/no-allocator/Cargo.toml create mode 100644 embedded/no-allocator/README.md create mode 100644 embedded/no-allocator/src/main.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9cae1f256..5ccc82c61 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -90,3 +90,19 @@ jobs: RUSTFLAGS: "-C link-arg=-Tlink.x" CARGO_TARGET_THUMBV7M_NONE_EABI_RUNNER: "qemu-system-arm -cpu cortex-m3 -machine mps2-an385 -nographic -semihosting-config enable=on,target=native -kernel" run: cd embedded/with-allocator && cargo run --target thumbv7m-none-eabi + + EmbeddedNoAlloc: + name: no_std no alloc + runs-on: ubuntu-latest + strategy: + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: rustc + args: -- -C link-arg=-nostartfiles diff --git a/.gitignore b/.gitignore index 9aa70d3ef..2cee415db 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,7 @@ *.o /Cargo.lock +/embedded/no-allocator/Cargo.lock +/embedded/no-allocator/.cargo /embedded/with-allocator/Cargo.lock /embedded/with-allocator/.cargo diff --git a/embedded/no-allocator/Cargo.toml b/embedded/no-allocator/Cargo.toml new file mode 100644 index 000000000..72f6fda4a --- /dev/null +++ b/embedded/no-allocator/Cargo.toml @@ -0,0 +1,15 @@ +[package] +authors = ["Tobin C. Harding "] +edition = "2018" +readme = "README.md" +name = "no-allocator" +version = "0.1.0" + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" + +[dependencies] +bech32 = { path = "../../", default_features = false } diff --git a/embedded/no-allocator/README.md b/embedded/no-allocator/README.md new file mode 100644 index 000000000..07e3766f3 --- /dev/null +++ b/embedded/no-allocator/README.md @@ -0,0 +1,10 @@ +# no_std test crate without an allocator + +This crate is based on the blog post found at: + + https://blog.dbrgn.ch/2019/12/24/testing-for-no-std-compatibility/ + +Its purpose is to test that the `rust-bech32` library can be built in a `no_std` environment without +a global allocator. + +Build with: `cargo rustc -- -C link-arg=-nostartfiles`. diff --git a/embedded/no-allocator/src/main.rs b/embedded/no-allocator/src/main.rs new file mode 100644 index 000000000..d723e82b8 --- /dev/null +++ b/embedded/no-allocator/src/main.rs @@ -0,0 +1,26 @@ +//! Test `no_std` build of `bech32`. +//! +//! Build with: `cargo rustc -- -C link-arg=-nostartfiles`. +//! + +#![no_std] +#![no_main] + +use core::panic::PanicInfo; + +// Note: `#[global_allocator]` is NOT set. + +#[allow(unused_imports)] +use bech32; + +/// This function is called on panic, defining this ensures build will fail if `std` is enabled +/// because `panic` will be defined twice. +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + loop {} +} + +#[no_mangle] +pub extern "C" fn _start() -> ! { + loop {} +} From e15eb2800f9b6916e799683ced910462c12433e8 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 28 Jul 2022 11:09:39 +1000 Subject: [PATCH 5/5] Add CI test.sh script We now have two features that require testing in various combinations, add a `contrib/test.sh` script to do the testing and use it in the `Test` CI job. Add names to other steps in the job to improve clarity. --- .github/workflows/rust.yml | 15 ++++++--------- contrib/test.sh | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 9 deletions(-) create mode 100755 contrib/test.sh diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 5ccc82c61..30d8ac02f 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -4,7 +4,7 @@ on: [pull_request] name: Continuous Integration jobs: - test: + Test: name: Test Suite runs-on: ubuntu-latest strategy: @@ -14,20 +14,17 @@ jobs: - stable - nightly steps: + - name: Checkout Crate - uses: actions/checkout@v2 + - name: Checkout Toolchain - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.rust }} override: true - - uses: actions-rs/cargo@v1 - with: - command: test - args: --verbose --no-default-features --features strict alloc - - uses: actions-rs/cargo@v1 - with: - command: test - args: --verbose --no-default-features --features strict std + - name: Run Test Script + env: ${{ matrix.rust }} + run: ./contrib/test.sh fmt: name: Rustfmt diff --git a/contrib/test.sh b/contrib/test.sh new file mode 100755 index 000000000..c808b777f --- /dev/null +++ b/contrib/test.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# +# CI test script for rust-bech32. +# +# The "strict" feature is used to configure cargo to deny all warnings, always use it in test runs. + +set -ex + +# Sanity, check tools exist. +cargo --version +rustc --version + +# Sanity, first check with default features. + +cargo build +cargo test + +# Sanity, build with no features. + +cargo build --no-default-features --features="strict" + +# Check "alloc" feature alone. + +cargo build --no-default-features --features="strict std" +cargo test --no-default-features --features="strict std" + +# Check "std" feature (implies "alloc"). + +cargo build --no-default-features --features="strict alloc" +cargo test --no-default-features --features="strict alloc" + +exit 0