Skip to content

Add "alloc" feature #71

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 1, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 25 additions & 7 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ on: [pull_request]
name: Continuous Integration

jobs:
test:
Test:
name: Test Suite
runs-on: ubuntu-latest
strategy:
@@ -13,16 +13,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 --features strict
- name: Run Test Script
env: ${{ matrix.rust }}
run: ./contrib/test.sh

fmt:
name: Rustfmt
@@ -60,7 +61,8 @@ jobs:
command: clippy
args: -- -D warnings

Embedded:
EmbeddedWithAlloc:
name: no_std with alloc
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -79,4 +81,20 @@ 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

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
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -2,5 +2,7 @@
*.o
/Cargo.lock

/embedded/Cargo.lock
/embedded/.cargo
/embedded/no-allocator/Cargo.lock
/embedded/no-allocator/.cargo
/embedded/with-allocator/Cargo.lock
/embedded/with-allocator/.cargo
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -13,7 +13,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 = []

25 changes: 25 additions & 0 deletions contrib/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/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

# Check without features ("strict" is a CI feature only, see above).
cargo build --no-default-features --features="strict"
cargo test --no-default-features --features="strict"

# Check "std" feature (implies "alloc").
cargo build --no-default-features --features="strict std"
cargo test --no-default-features --features="strict std"

# Check "alloc" feature alone.
cargo build --no-default-features --features="strict alloc"
cargo test --no-default-features --features="strict alloc"

exit 0
15 changes: 15 additions & 0 deletions embedded/no-allocator/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
authors = ["Tobin C. Harding <me@tobin.cc>"]
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 }
10 changes: 10 additions & 0 deletions embedded/no-allocator/README.md
Original file line number Diff line number Diff line change
@@ -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`.
26 changes: 26 additions & 0 deletions embedded/no-allocator/src/main.rs
Original file line number Diff line number Diff line change
@@ -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 {}
}
6 changes: 3 additions & 3 deletions embedded/Cargo.toml → embedded/with-allocator/Cargo.toml
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
authors = ["Riccardo Casatta <riccardo@casatta.it>"]
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

File renamed without changes.
File renamed without changes.
35 changes: 30 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -57,19 +57,21 @@ assert_eq!(variant, Variant::Bech32);
#![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(any(test, feature = "std"))]
extern crate core;

#[cfg(all(not(feature = "std"), not(test)))]
#[cfg(all(feature = "alloc", not(feature = "std")))]
use alloc::borrow::Cow;
#[cfg(all(not(feature = "std"), not(test)))]
#[cfg(all(feature = "alloc", not(feature = "std")))]
use alloc::{string::String, vec::Vec};
use core::convert::{Infallible, TryFrom};
#[cfg(feature = "alloc")]
use core::convert::Infallible;
use core::convert::TryFrom;
use core::{fmt, mem};
#[cfg(any(feature = "std", test))]
#[cfg(feature = "std")]
use std::borrow::Cow;

/// Integer in the range `0..32`
@@ -220,6 +222,7 @@ pub trait FromBase32: Sized {
fn from_base32(b32: &[u5]) -> Result<Self, Self::Err>;
}

#[cfg(feature = "alloc")]
impl WriteBase32 for Vec<u5> {
type Err = Infallible;

@@ -234,6 +237,7 @@ impl WriteBase32 for Vec<u5> {
}
}

#[cfg(feature = "alloc")]
impl FromBase32 for Vec<u8> {
type Err = Error;

@@ -243,6 +247,7 @@ impl FromBase32 for Vec<u8> {
}

/// 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<u5> {
@@ -257,11 +262,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<T: AsRef<[u8]>> ToBase32 for T {
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
// Amount of bits left over from last round, stored in buffer.
@@ -306,6 +313,7 @@ impl<T: AsRef<[u8]>> ToBase32 for T {
}
}

#[cfg(feature = "alloc")]
impl<T: AsRef<[u8]>> Base32Len for T {
fn base32_len(&self) -> usize {
let bits = self.as_ref().len() * 8;
@@ -327,6 +335,7 @@ pub trait CheckBase32<T: AsRef<[u5]>> {
fn check_base32(self) -> Result<T, Self::Err>;
}

#[cfg(feature = "alloc")]
impl<T: AsRef<[u8]>> CheckBase32<Vec<u5>> for T {
type Err = Error;

@@ -336,6 +345,7 @@ impl<T: AsRef<[u8]>> CheckBase32<Vec<u5>> for T {
}

#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg(feature = "alloc")]
enum Case {
Upper,
Lower,
@@ -348,6 +358,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<Case, Error> {
if hrp.is_empty() || hrp.len() > 83 {
return Err(Error::InvalidLength);
@@ -387,6 +398,7 @@ fn check_hrp(hrp: &str) -> Result<Case, Error> {
/// * 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<T: AsRef<[u5]>>(
fmt: &mut fmt::Write,
hrp: &str,
@@ -416,6 +428,7 @@ pub fn encode_to_fmt<T: AsRef<[u5]>>(
/// * 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<T: AsRef<[u5]>>(
fmt: &mut fmt::Write,
hrp: &str,
@@ -454,6 +467,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<Self> {
match c {
BECH32_CONST => Some(Variant::Bech32),
@@ -476,6 +490,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<T: AsRef<[u5]>>(hrp: &str, data: T, variant: Variant) -> Result<String, Error> {
let mut buf = String::new();
encode_to_fmt(&mut buf, hrp, data, variant)?.unwrap();
@@ -488,6 +503,7 @@ pub fn encode<T: AsRef<[u5]>>(hrp: &str, data: T, variant: Variant) -> Result<St
/// * 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<T: AsRef<[u5]>>(hrp: &str, data: T) -> Result<String, Error> {
let mut buf = String::new();
encode_without_checksum_to_fmt(&mut buf, hrp, data)?.unwrap();
@@ -497,6 +513,7 @@ pub fn encode_without_checksum<T: AsRef<[u5]>>(hrp: &str, data: T) -> Result<Str
/// Decode a bech32 string into the raw HRP and the data bytes.
///
/// Returns the HRP in lowercase, the data with the checksum removed, and the encoding.
#[cfg(feature = "alloc")]
pub fn decode(s: &str) -> Result<(String, Vec<u5>, Variant), Error> {
let (hrp_lower, mut data) = split_and_decode(s)?;
if data.len() < CHECKSUM_LENGTH {
@@ -518,9 +535,11 @@ pub fn decode(s: &str) -> Result<(String, Vec<u5>, 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<u5>), 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<u5>), Error> {
// Split at separator and check for two pieces
let (raw_hrp, raw_data) = match s.rfind(SEP) {
@@ -577,12 +596,14 @@ fn split_and_decode(s: &str) -> Result<(String, Vec<u5>), Error> {
Ok((hrp_lower, data))
}

#[cfg(feature = "alloc")]
fn verify_checksum(hrp: &[u8], data: &[u5]) -> Option<Variant> {
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<u5> {
let mut v: Vec<u5> = Vec::new();
for b in hrp {
@@ -595,6 +616,7 @@ fn hrp_expand(hrp: &[u8]) -> Vec<u5> {
v
}

#[cfg(feature = "alloc")]
fn polymod(values: &[u5]) -> u32 {
let mut chk: u32 = 1;
let mut b: u8;
@@ -623,6 +645,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,
@@ -697,6 +720,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<T>(data: &[T], from: u32, to: u32, pad: bool) -> Result<Vec<u8>, Error>
where
T: Into<u8> + Copy,
@@ -732,6 +756,7 @@ where
}

#[cfg(test)]
#[cfg(feature = "alloc")] // Note, all the unit tests currently require an allocator.
mod tests {
use super::*;