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
Show file tree
Hide file tree
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
Expand Up @@ -3,7 +3,7 @@ on: [pull_request]
name: Continuous Integration

jobs:
test:
Test:
name: Test Suite
runs-on: ubuntu-latest
strategy:
Expand All @@ -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
Expand Down Expand Up @@ -60,7 +61,8 @@ jobs:
command: clippy
args: -- -D warnings

Embedded:
EmbeddedWithAlloc:
name: no_std with alloc
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -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
Expand Up @@ -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
Expand Up @@ -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 = []

Expand Down
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 <[email protected]>"]
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
Expand Up @@ -2,7 +2,7 @@
authors = ["Riccardo Casatta <[email protected]>"]
edition = "2018"
readme = "README.md"
name = "embedded"
name = "with-allocator"
version = "0.1.0"

[dependencies]
Expand All @@ -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

Expand Down
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
Expand Up @@ -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`
Expand Down Expand Up @@ -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;

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

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

Expand All @@ -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> {
Expand All @@ -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.
Expand Down Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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 {
Expand All @@ -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) {
Expand Down Expand Up @@ -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 {
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -732,6 +756,7 @@ where
}

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

Expand Down