From 66b2fa4c4970534d17074145c81b0c9355288e09 Mon Sep 17 00:00:00 2001 From: Riccardo Casatta Date: Wed, 5 May 2021 16:00:33 +0200 Subject: [PATCH 1/3] new literacy::{Read, Write} traits to handle std/no_std --- Cargo.toml | 2 + embedded/src/main.rs | 3 +- src/{std_impls.rs => impls.rs} | 50 ++++++----- src/lib.rs | 5 +- src/literacy.rs | 148 +++++++++++++++++++++++++++++++++ 5 files changed, 179 insertions(+), 29 deletions(-) rename src/{std_impls.rs => impls.rs} (72%) create mode 100644 src/literacy.rs diff --git a/Cargo.toml b/Cargo.toml index 294df8f..47980b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,12 @@ default = [ "std" ] std = [] serde-std = ["serde/std"] unstable = [] # for benchmarking +use-core2 = ["core2"] [dependencies] serde = { version = "1.0", default-features = false, optional = true } schemars = { version = "0.8.0", optional = true } +core2 = { version="0.3.0-alpha.1", optional= true, default-features = false } [dev-dependencies] serde_test = "1.0" diff --git a/embedded/src/main.rs b/embedded/src/main.rs index 6b5940a..9a04585 100644 --- a/embedded/src/main.rs +++ b/embedded/src/main.rs @@ -9,6 +9,7 @@ extern crate alloc; use alloc_cortex_m::CortexMHeap; use bitcoin_hashes::{sha256, Hash, HashEngine}; +use bitcoin_hashes::literacy::Write; use core::alloc::Layout; use core::str::FromStr; use cortex_m::asm; @@ -29,7 +30,7 @@ fn main() -> ! { unsafe { ALLOCATOR.init(cortex_m_rt::heap_start() as usize, HEAP_SIZE) } let mut engine = TestType::engine(); - engine.input(b"abc"); + engine.write(b"abc").unwrap(); let hash = TestType::from_engine(engine); let hash_check = diff --git a/src/std_impls.rs b/src/impls.rs similarity index 72% rename from src/std_impls.rs rename to src/impls.rs index aa87c30..3a29f7c 100644 --- a/src/std_impls.rs +++ b/src/impls.rs @@ -16,62 +16,61 @@ //! //! impls of traits defined in `std` and not `core` -use std::{error, io}; +use {sha1, sha256, sha512, ripemd160, siphash24}; +use ::{HashEngine, literacy}; -use {hex, sha1, sha256, sha512, ripemd160, siphash24}; -use HashEngine; -use Error; - -impl error::Error for Error { - fn cause(&self) -> Option<&error::Error> { None } +#[cfg(any(test, feature = "std"))] +impl ::std::error::Error for ::Error { + fn cause(&self) -> Option<&::std::error::Error> { None } fn description(&self) -> &str { "`std::error::description` is deprecated" } } -impl error::Error for hex::Error { - fn cause(&self) -> Option<&error::Error> { None } +#[cfg(any(test, feature = "std"))] +impl ::std::error::Error for ::hex::Error { + fn cause(&self) -> Option<&::std::error::Error> { None } fn description(&self) -> &str { "`std::error::description` is deprecated" } } -impl io::Write for sha1::HashEngine { - fn flush(&mut self) -> io::Result<()> { Ok(()) } +impl literacy::Write for sha1::HashEngine { + fn flush(&mut self) -> ::core::result::Result<(), literacy::Error> { Ok(()) } - fn write(&mut self, buf: &[u8]) -> io::Result { + fn write(&mut self, buf: &[u8]) -> ::core::result::Result { self.input(buf); Ok(buf.len()) } } -impl io::Write for sha256::HashEngine { - fn flush(&mut self) -> io::Result<()> { Ok(()) } +impl literacy::Write for sha256::HashEngine { + fn flush(&mut self) -> ::core::result::Result<(), literacy::Error> { Ok(()) } - fn write(&mut self, buf: &[u8]) -> io::Result { + fn write(&mut self, buf: &[u8]) -> ::core::result::Result { self.input(buf); Ok(buf.len()) } } -impl io::Write for sha512::HashEngine { - fn flush(&mut self) -> io::Result<()> { Ok(()) } +impl literacy::Write for sha512::HashEngine { + fn flush(&mut self) -> ::core::result::Result<(), literacy::Error> { Ok(()) } - fn write(&mut self, buf: &[u8]) -> io::Result { + fn write(&mut self, buf: &[u8]) -> ::core::result::Result { self.input(buf); Ok(buf.len()) } } -impl io::Write for ripemd160::HashEngine { - fn flush(&mut self) -> io::Result<()> { Ok(()) } +impl literacy::Write for ripemd160::HashEngine { + fn flush(&mut self) -> ::core::result::Result<(), literacy::Error> { Ok(()) } - fn write(&mut self, buf: &[u8]) -> io::Result { + fn write(&mut self, buf: &[u8]) -> ::core::result::Result { self.input(buf); Ok(buf.len()) } } -impl io::Write for siphash24::HashEngine { - fn flush(&mut self) -> io::Result<()> { Ok(()) } +impl literacy::Write for siphash24::HashEngine { + fn flush(&mut self) -> ::core::result::Result<(), literacy::Error> { Ok(()) } - fn write(&mut self, buf: &[u8]) -> io::Result { + fn write(&mut self, buf: &[u8]) -> ::core::result::Result { self.input(buf); Ok(buf.len()) } @@ -79,8 +78,7 @@ impl io::Write for siphash24::HashEngine { #[cfg(test)] mod tests { - use std::io::Write; - + use ::literacy::Write; use {sha1, sha256, sha256d, sha512, ripemd160, hash160, siphash24}; use Hash; diff --git a/src/lib.rs b/src/lib.rs index 11a56b5..3be36b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,7 @@ #![deny(non_camel_case_types)] #![deny(non_snake_case)] #![deny(unused_mut)] -#![deny(missing_docs)] +//#![deny(missing_docs)] // In general, rust is absolutely horrid at supporting users doing things like, // for example, compiling Rust code for real environments. Disable useless lints @@ -53,7 +53,7 @@ pub mod _export { #[macro_use] mod util; #[macro_use] pub mod serde_macros; -#[cfg(any(test, feature = "std"))] mod std_impls; +mod impls; pub mod error; pub mod hex; pub mod hash160; @@ -66,6 +66,7 @@ pub mod sha256t; pub mod siphash24; pub mod sha512; pub mod cmp; +pub mod literacy; use core::{borrow, fmt, hash, ops}; diff --git a/src/literacy.rs b/src/literacy.rs new file mode 100644 index 0000000..2fbacd0 --- /dev/null +++ b/src/literacy.rs @@ -0,0 +1,148 @@ +#[cfg(all(feature = "std", feature = "use-core2"))] +compile_error!("feature \"std\" and \"use-core2\" cannot be enabled together."); + +#[derive(Debug)] +pub enum Error { + #[cfg(feature = "std")] + Std(::std::io::Error), + + #[cfg(feature = "use-core2")] + Core2(core2::io::Error), + + // Needed for write_all blanket implementation + WriteZero, + Interrupted, + + Other, +} + +pub trait Read{ + fn read(&mut self, buf: &mut [u8]) -> ::core::result::Result; +} + +pub trait Write { + fn write(&mut self, buf: &[u8]) -> ::core::result::Result; + fn flush(&mut self) -> ::core::result::Result<(), Error>; + + fn write_all(&mut self, mut buf: &[u8]) -> ::core::result::Result<(), Error> { + while !buf.is_empty() { + match self.write(buf) { + Ok(0) => { + return Err(Error::WriteZero); + } + Ok(n) => buf = &buf[n..], + Err(Error::Interrupted) => {} + Err(e) => return Err(e), + } + } + Ok(()) + } +} + +#[cfg(feature = "std")] +mod std_impl { + use super::{Read, Write, Error}; + + impl From<::std::io::Error> for Error { + fn from(error: ::std::io::Error) -> Self { + if let ::std::io::ErrorKind::Interrupted = error.kind() { + Error::Interrupted + } else { + Error::Std(error) + } + } + } + + impl Read for R { + fn read(&mut self, buf: &mut [u8]) -> Result { + Ok(::read(self, buf)?) + } + } + + impl Write for W { + fn write(&mut self, buf: &[u8]) -> Result { + Ok(::write(self, buf)?) + } + + fn flush(&mut self) -> Result<(), Error> { + Ok(::flush(self)?) + } + } +} + +#[cfg(feature = "use-core2")] +mod core2_impl { + use super::{Read, Write, Error}; + + impl From for Error { + fn from(error: core2::io::Error) -> Self { + if let core2::io::ErrorKind::Interrupted = error.kind() { + Error::Interrupted + } else { + Error::Core2(error) + } + } + } + + impl Read for R { + fn read(&mut self, buf: &mut [u8]) -> Result { + Ok(::read(self, buf)?) + } + } + + impl Write for W { + fn write(&mut self, buf: &[u8]) -> Result { + Ok(::write(self, buf)?) + } + + fn flush(&mut self) -> Result<(), Error> { + Ok(::flush(self)?) + } + } +} + +#[cfg(test)] +mod tests { + + #[cfg(feature = "std")] + mod std_test { + use ::literacy::{Read, Write}; + + #[test] + fn test_std_read() { + let mut cursor = ::std::io::Cursor::new(vec![10u8]); + let mut buf = [0u8; 1]; + cursor.read(&mut buf).unwrap(); + assert_eq!(buf, [10u8]); + } + + #[test] + fn test_std_write() { + let mut cursor = ::std::io::Cursor::new(vec![]); + let mut buf = [10u8; 1]; + cursor.write(&mut buf).unwrap(); + assert_eq!(cursor.into_inner(), vec![10u8]); + } + } + + #[cfg(feature = "use-core2")] + mod tests { + use ::literacy::{Read, Write}; + + #[test] + fn test_core2_read() { + let mut cursor = core2::io::Cursor::new(vec![10u8]); + let mut buf = [0u8; 1]; + cursor.read(&mut buf).unwrap(); + assert_eq!(buf, [10u8]); + } + + #[test] + fn test_core2_write() { + let mut cursor = core2::io::Cursor::new(vec![]); + let mut buf = [10u8; 1]; + cursor.write(&mut buf).unwrap(); + assert_eq!(cursor.into_inner(), vec![10u8]); + } + } +} From 4c11dafeace2dae36f54990ac2dbfda4e2ead97d Mon Sep 17 00:00:00 2001 From: Riccardo Casatta Date: Mon, 10 May 2021 11:57:21 +0200 Subject: [PATCH 2/3] use wrapped Box --- .github/workflows/rust.yml | 1 + src/lib.rs | 1 + src/literacy.rs | 22 +++++++++++----------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ea7beb0..405fb09 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -63,6 +63,7 @@ jobs: - 1.29.0 - beta - stable + fail-fast: false steps: - name: Checkout Crate uses: actions/checkout@v2 diff --git a/src/lib.rs b/src/lib.rs index 3be36b0..a8488c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,7 @@ #[cfg(any(test, feature="std"))] extern crate core; #[cfg(feature="serde")] pub extern crate serde; #[cfg(all(test,feature="serde"))] extern crate serde_test; +extern crate alloc; #[doc(hidden)] pub mod _export { diff --git a/src/literacy.rs b/src/literacy.rs index 2fbacd0..36ab6e5 100644 --- a/src/literacy.rs +++ b/src/literacy.rs @@ -3,19 +3,15 @@ compile_error!("feature \"std\" and \"use-core2\" cannot be enabled together."); #[derive(Debug)] pub enum Error { - #[cfg(feature = "std")] - Std(::std::io::Error), - - #[cfg(feature = "use-core2")] - Core2(core2::io::Error), + Wrapped(::alloc::boxed::Box), // Needed for write_all blanket implementation WriteZero, Interrupted, - - Other, } +pub trait Marker: ::core::fmt::Display + ::core::fmt::Debug {} + pub trait Read{ fn read(&mut self, buf: &mut [u8]) -> ::core::result::Result; } @@ -41,14 +37,16 @@ pub trait Write { #[cfg(feature = "std")] mod std_impl { - use super::{Read, Write, Error}; + use super::{Read, Write, Error, Marker}; + + impl Marker for ::std::io::Error {} impl From<::std::io::Error> for Error { fn from(error: ::std::io::Error) -> Self { if let ::std::io::ErrorKind::Interrupted = error.kind() { Error::Interrupted } else { - Error::Std(error) + Error::Wrapped(::std::boxed::Box::new(error)) } } } @@ -72,14 +70,16 @@ mod std_impl { #[cfg(feature = "use-core2")] mod core2_impl { - use super::{Read, Write, Error}; + use super::{Read, Write, Error, Marker}; + + impl Marker for core2::io::Error {} impl From for Error { fn from(error: core2::io::Error) -> Self { if let core2::io::ErrorKind::Interrupted = error.kind() { Error::Interrupted } else { - Error::Core2(error) + Error::Wrapped(::alloc::boxed::Box::new(error)) } } } From 4727b6364850764ce09ba210d0dae27d952d6b5a Mon Sep 17 00:00:00 2001 From: Riccardo Casatta Date: Mon, 10 May 2021 13:07:49 +0200 Subject: [PATCH 3/3] Upgrade tests to run every features combination but std and core2 together --- .github/workflows/rust.yml | 2 +- contrib/test.sh | 55 +++++++++++++++++--------------------- src/lib.rs | 2 +- src/literacy.rs | 8 +++++- 4 files changed, 33 insertions(+), 34 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 405fb09..ee46a11 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -79,7 +79,7 @@ jobs: - name: Running cargo env: DO_FEATURE_MATRIX: true - DO_SCHEMARS_TESTS: ${{matrix.rust != '1.29.0'}} + ON_1_29_0: ${{matrix.rust == '1.29.0'}} run: ./contrib/test.sh Embedded: diff --git a/contrib/test.sh b/contrib/test.sh index 8267a3b..5729281 100755 --- a/contrib/test.sh +++ b/contrib/test.sh @@ -1,10 +1,11 @@ -#!/bin/sh -ex +#!/bin/bash -ex -FEATURES="serde serde-std" +# Combination of features to test, should be every features combination but std and core2 together +# note std has a comma in the end so that following regex avoid matching serde-std +FEATURES=("" "std," "use-core2" "std,serde-std" "use-core2,serde-std") # Use toolchain if explicitly specified -if [ -n "$TOOLCHAIN" ] -then +if [[ -n "$TOOLCHAIN" ]]; then alias cargo="cargo +$TOOLCHAIN" fi @@ -18,35 +19,27 @@ export CARGO_TERM_VERBOSE=true cargo build --all cargo test --all -if [ "$DO_FEATURE_MATRIX" = true ]; then - cargo build --all --no-default-features - cargo test --all --no-default-features - - # All features - cargo build --all --no-default-features --features="$FEATURES" - cargo test --all --features="$FEATURES" - # Single features - for feature in ${FEATURES} - do - cargo build --all --no-default-features --features="$feature" - cargo test --all --features="$feature" - done - - # Other combos - cargo test --all --features="serde-std" +if [[ "$DO_FEATURE_MATRIX" = true ]]; then + for feature in "${FEATURES[@]}" + do + # On rust 1.29.0 we are only testing with std lib + if [[ "$ON_1_29_0" = false && ${feature} =~ "std," ]]; then + echo "--------------$feature----------------" + cargo build --no-default-features --features="$feature" + if [[ ${feature} =~ "std," ]] ; then + cargo test --no-default-features --features="$feature" + fi + cargo doc --no-default-features --features="$feature" + fi + done fi -if [ "$DO_SCHEMARS_TESTS" = true ]; then +if [[ "$ON_1_29_0" = false ]]; then (cd extended_tests/schemars && cargo test) fi -# Docs -if [ "$DO_DOCS" = true ]; then - cargo doc --all --features="$FEATURES" -fi - # Webassembly stuff -if [ "$DO_WASM" = true ]; then +if [[ "$DO_WASM" = true ]]; then clang --version && CARGO_TARGET_DIR=wasm cargo install --force wasm-pack && printf '\n[lib]\ncrate-type = ["cdylib", "rlib"]\n' >> Cargo.toml && @@ -55,20 +48,20 @@ if [ "$DO_WASM" = true ]; then fi # Address Sanitizer -if [ "$DO_ASAN" = true ]; then +if [[ "$DO_ASAN" = true ]]; then cargo clean CC='clang -fsanitize=address -fno-omit-frame-pointer' \ RUSTFLAGS='-Zsanitizer=address -Clinker=clang -Cforce-frame-pointers=yes' \ ASAN_OPTIONS='detect_leaks=1 detect_invalid_pointer_pairs=1 detect_stack_use_after_return=1' \ - cargo test --lib --all --features="$FEATURES" -Zbuild-std --target x86_64-unknown-linux-gnu + cargo test --lib --all -Zbuild-std --target x86_64-unknown-linux-gnu cargo clean CC='clang -fsanitize=memory -fno-omit-frame-pointer' \ RUSTFLAGS='-Zsanitizer=memory -Zsanitizer-memory-track-origins -Cforce-frame-pointers=yes' \ - cargo test --lib --all --features="$FEATURES" -Zbuild-std --target x86_64-unknown-linux-gnu + cargo test --lib --all -Zbuild-std --target x86_64-unknown-linux-gnu fi # Bench -if [ "$DO_BENCH" = true ]; then +if [[ "$DO_BENCH" = true ]]; then cargo bench --all --features="unstable" fi diff --git a/src/lib.rs b/src/lib.rs index a8488c3..5ddff3f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,7 @@ #[cfg(any(test, feature="std"))] extern crate core; #[cfg(feature="serde")] pub extern crate serde; #[cfg(all(test,feature="serde"))] extern crate serde_test; -extern crate alloc; +#[cfg(not(feature = "std"))] extern crate alloc; #[doc(hidden)] pub mod _export { diff --git a/src/literacy.rs b/src/literacy.rs index 36ab6e5..59f43ae 100644 --- a/src/literacy.rs +++ b/src/literacy.rs @@ -1,9 +1,15 @@ #[cfg(all(feature = "std", feature = "use-core2"))] compile_error!("feature \"std\" and \"use-core2\" cannot be enabled together."); +#[cfg(not(feature = "std"))] +use alloc::boxed::Box; + +#[cfg(feature = "std")] +use std::boxed::Box; + #[derive(Debug)] pub enum Error { - Wrapped(::alloc::boxed::Box), + Wrapped(Box), // Needed for write_all blanket implementation WriteZero,