diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ea7beb0..0f174ae 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -11,7 +11,7 @@ jobs: rust: - nightly steps: - - name: Checkout Crate + - name: Checkout Crate uses: actions/checkout@v2 - name: Checkout Toolchain uses: actions-rs/toolchain@v1 @@ -29,7 +29,7 @@ jobs: DO_BENCH: true run: ./contrib/test.sh - wasm: + wasm: name: Stable - Docs / WebAssembly Build runs-on: ubuntu-latest strategy: @@ -63,6 +63,7 @@ jobs: - 1.29.0 - beta - stable + fail-fast: false steps: - name: Checkout Crate uses: actions/checkout@v2 @@ -78,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: @@ -102,4 +103,3 @@ jobs: CARGO_TARGET_THUMBV7M_NONE_EABI_RUNNER: "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" run: cd embedded && cargo run --target thumbv7m-none-eabi - diff --git a/Cargo.toml b/Cargo.toml index 294df8f..42a06fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,13 @@ default = [ "std" ] std = [] serde-std = ["serde/std"] unstable = [] # for benchmarking +use-core2 = ["core2"] +use-core2-std = ["use-core2", "core2/std"] [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/contrib/test.sh b/contrib/test.sh index 8267a3b..db976cb 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 +# note std has a comma in the end so that following regex avoid matching serde-std +FEATURES=("" "std," "std,serde" "serde" "use-core2,serde" "use-core2" "use-core2-std" "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 and without use-core2 + if [[ "$ON_1_29_0" = false || (${feature} =~ "std," && ! ${feature} =~ "use-core2") ]]; 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,19 @@ 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 --all-features -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 --all-features -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/embedded/src/main.rs b/embedded/src/main.rs index 6b5940a..957c02a 100644 --- a/embedded/src/main.rs +++ b/embedded/src/main.rs @@ -8,7 +8,8 @@ extern crate bitcoin_hashes; extern crate alloc; use alloc_cortex_m::CortexMHeap; -use bitcoin_hashes::{sha256, Hash, HashEngine}; +use bitcoin_hashes::{sha256, Hash}; +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/error.rs b/src/error.rs index 8c87f62..9e0399a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,7 +17,7 @@ use core::fmt; -/// [bitcoin_hashes] error. +/// This crate error. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Error { /// Tried to create a fixed-length hash from a slice with the wrong size (expected, got). diff --git a/src/std_impls.rs b/src/impls.rs similarity index 65% rename from src/std_impls.rs rename to src/impls.rs index aa87c30..7d88a8e 100644 --- a/src/std_impls.rs +++ b/src/impls.rs @@ -15,72 +15,70 @@ //! `std` Impls //! //! impls of traits defined in `std` and not `core` +//! +//! Note engines implementation cannot error, however, we use [crate::literacy::Error] type for +//! more ergonomic use inside other methods returning errors. -use std::{error, io}; - -use {hex, sha1, sha256, sha512, ripemd160, siphash24}; -use HashEngine; -use Error; +use {sha1, sha256, sha512, ripemd160, siphash24}; +use ::{HashEngine, literacy}; -impl error::Error for Error { - fn cause(&self) -> Option<&error::Error> { None } - fn description(&self) -> &str { "`std::error::description` is deprecated" } -} +macro_rules! write_impl { + ($HashEngine: ty) => { + impl literacy::Write for $HashEngine { + type Error = literacy::Error; -impl error::Error for hex::Error { - fn cause(&self) -> Option<&error::Error> { None } - fn description(&self) -> &str { "`std::error::description` is deprecated" } -} + fn write(&mut self, buf: &[u8]) -> ::core::result::Result { + self.input(buf); + Ok(buf.len()) + } -impl io::Write for sha1::HashEngine { - fn flush(&mut self) -> io::Result<()> { Ok(()) } + fn write_all(&mut self, buf: &[u8]) -> ::core::result::Result<(), Self::Error> { + self.write(buf)?; + Ok(()) + } - fn write(&mut self, buf: &[u8]) -> io::Result { - self.input(buf); - Ok(buf.len()) - } -} + fn flush(&mut self) -> ::core::result::Result<(), Self::Error> { Ok(()) } + } -impl io::Write for sha256::HashEngine { - fn flush(&mut self) -> io::Result<()> { Ok(()) } + impl<'a> literacy::Write for &'a mut $HashEngine { + type Error = literacy::Error; - fn write(&mut self, buf: &[u8]) -> io::Result { - self.input(buf); - Ok(buf.len()) - } -} + fn write<'b>(&'b mut self, buf: &'b [u8]) -> ::core::result::Result { + self.input(buf); + Ok(buf.len()) + } -impl io::Write for sha512::HashEngine { - fn flush(&mut self) -> io::Result<()> { Ok(()) } + fn write_all<'b>(&'b mut self, buf: &'b [u8]) -> ::core::result::Result<(), Self::Error> { + self.write(buf)?; + Ok(()) + } - fn write(&mut self, buf: &[u8]) -> io::Result { - self.input(buf); - Ok(buf.len()) - } + fn flush<'b>(&'b mut self) -> ::core::result::Result<(), Self::Error> { Ok(()) } + } + }; } -impl io::Write for ripemd160::HashEngine { - fn flush(&mut self) -> io::Result<()> { Ok(()) } - - fn write(&mut self, buf: &[u8]) -> io::Result { - self.input(buf); - Ok(buf.len()) - } +#[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 io::Write for siphash24::HashEngine { - fn flush(&mut self) -> io::Result<()> { Ok(()) } - - fn write(&mut self, buf: &[u8]) -> io::Result { - self.input(buf); - Ok(buf.len()) - } +#[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" } } +write_impl!(sha1::HashEngine); +write_impl!(sha256::HashEngine); +write_impl!(sha512::HashEngine); +write_impl!(ripemd160::HashEngine); +write_impl!(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..fcbc202 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; +#[cfg(feature = "use-core2")] pub extern crate core2; #[doc(hidden)] pub mod _export { @@ -51,9 +52,12 @@ pub mod _export { #[cfg(feature = "schemars")] extern crate schemars; +#[cfg(not(feature = "std"))] +extern crate alloc; + #[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 +70,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..4a28dea --- /dev/null +++ b/src/literacy.rs @@ -0,0 +1,345 @@ +// Bitcoin Hashes Library +// Written in 2021 by +// The rust-bitcoin developers. +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! # Literacy traits +//! +//! literacy: "the ability to [Read] and [Write]." +//! +//! * With `std` enabled, traits are automatically implemented for `std::io::{Read, Write}` +//! * Without `std` but using feature `use-core2`, they are implemented for `core2::io::{Read, Write}` +//! * Without neither `std` and nor `core2` default implementation `impl Read for &[u8]` +//! and `impl Write for ::alloc::vec::Vec` are provided +//! + +#[cfg(feature = "std")] +use std::boxed::Box as AllocBox; + +#[cfg(not(feature = "std"))] +use alloc::boxed::Box as AllocBox; + +/// The Read trait allows for reading bytes from a source. +pub trait Read { + /// The error type returned in Result + type Error: ErrorTrait; + + /// see [std::io::Read::read] + fn read(&mut self, buf: &mut [u8]) -> ::core::result::Result; + + /// see [std::io::Read::read_exact] + fn read_exact(&mut self, buf: &mut [u8]) -> ::core::result::Result<(), Self::Error>; +} + +/// The Write trait allows to write bytes in the object implementing it. +pub trait Write { + /// The error type returned in Result + type Error: ErrorTrait; + + /// see [std::io::Write::write] + fn write(&mut self, buf: &[u8]) -> ::core::result::Result; + + /// see [std::io::Write::write_all] + fn write_all(&mut self, buf: &[u8]) -> ::core::result::Result<(), Self::Error>; + + /// see [std::io::Write::flush] + fn flush(&mut self) -> ::core::result::Result<(), Self::Error>; +} + +/// The literacy Error trait, custom errors must implement this +pub trait ErrorTrait { + /// The error category + fn kind(&self) -> ErrorKind; +} + +/// Same as [std::io::ErrorKind] that we have to duplicate because ErrorKind is not in `core` +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[allow(missing_docs)] +pub enum ErrorKind { + NotFound, + PermissionDenied, + ConnectionRefused, + ConnectionReset, + ConnectionAborted, + NotConnected, + AddrInUse, + AddrNotAvailable, + BrokenPipe, + AlreadyExists, + WouldBlock, + InvalidInput, + InvalidData, + TimedOut, + WriteZero, + Interrupted, + Other, + UnexpectedEof, +} + +/// The Error type in case we are not using [std::io::Error] or [core2::io::Error] +#[derive(Debug)] +pub struct Error { + kind: ErrorKind, + + error: AllocBox, +} + +trait InnerError: ::core::fmt::Debug + ::core::any::Any {} + +impl InnerError for () {} + +impl ErrorTrait for Error { + fn kind(&self) -> ErrorKind { + self.kind + } +} + +#[cfg(all(feature = "std", not(feature = "use-core2")))] +impl ErrorTrait for ::std::io::Error { + fn kind(&self) -> ErrorKind { + match self.kind() { + ::std::io::ErrorKind::NotFound => ErrorKind::NotFound, + ::std::io::ErrorKind::PermissionDenied => ErrorKind::PermissionDenied, + ::std::io::ErrorKind::ConnectionRefused => ErrorKind::ConnectionRefused, + ::std::io::ErrorKind::ConnectionReset => ErrorKind::ConnectionReset, + ::std::io::ErrorKind::ConnectionAborted => ErrorKind::ConnectionAborted, + ::std::io::ErrorKind::NotConnected => ErrorKind::NotConnected, + ::std::io::ErrorKind::AddrInUse => ErrorKind::AddrInUse, + ::std::io::ErrorKind::AddrNotAvailable => ErrorKind::AddrNotAvailable, + ::std::io::ErrorKind::BrokenPipe => ErrorKind::BrokenPipe, + ::std::io::ErrorKind::AlreadyExists => ErrorKind::AlreadyExists, + ::std::io::ErrorKind::WouldBlock => ErrorKind::WouldBlock, + ::std::io::ErrorKind::InvalidInput => ErrorKind::InvalidInput, + ::std::io::ErrorKind::InvalidData => ErrorKind::InvalidData, + ::std::io::ErrorKind::TimedOut => ErrorKind::TimedOut, + ::std::io::ErrorKind::WriteZero => ErrorKind::WriteZero, + ::std::io::ErrorKind::Interrupted => ErrorKind::Interrupted, + ::std::io::ErrorKind::Other => ErrorKind::Other, + ::std::io::ErrorKind::UnexpectedEof => ErrorKind::UnexpectedEof, + _ => ErrorKind::Other, + } + } +} + +#[cfg(all(feature = "std", not(feature = "use-core2")))] +mod std_impl { + use super::{Read, Write}; + + impl Read for R { + type Error = ::std::io::Error; + + fn read(&mut self, buf: &mut [u8]) -> Result { + ::read(self, buf) + } + + fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { + ::read_exact(self, buf) + } + } + + impl Write for W { + type Error = ::std::io::Error; + + fn write(&mut self, buf: &[u8]) -> Result { + ::write(self, buf) + } + + fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> { + ::write_all(self, buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + ::flush(self) + } + } +} + +#[cfg(feature = "use-core2")] +impl ErrorTrait for core2::io::Error { + fn kind(&self) -> ErrorKind { + match self.kind() { + core2::io::ErrorKind::NotFound => ErrorKind::NotFound, + core2::io::ErrorKind::PermissionDenied => ErrorKind::PermissionDenied, + core2::io::ErrorKind::ConnectionRefused => ErrorKind::ConnectionRefused, + core2::io::ErrorKind::ConnectionReset => ErrorKind::ConnectionReset, + core2::io::ErrorKind::ConnectionAborted => ErrorKind::ConnectionAborted, + core2::io::ErrorKind::NotConnected => ErrorKind::NotConnected, + core2::io::ErrorKind::AddrInUse => ErrorKind::AddrInUse, + core2::io::ErrorKind::AddrNotAvailable => ErrorKind::AddrNotAvailable, + core2::io::ErrorKind::BrokenPipe => ErrorKind::BrokenPipe, + core2::io::ErrorKind::AlreadyExists => ErrorKind::AlreadyExists, + core2::io::ErrorKind::WouldBlock => ErrorKind::WouldBlock, + core2::io::ErrorKind::InvalidInput => ErrorKind::InvalidInput, + core2::io::ErrorKind::InvalidData => ErrorKind::InvalidData, + core2::io::ErrorKind::TimedOut => ErrorKind::TimedOut, + core2::io::ErrorKind::WriteZero => ErrorKind::WriteZero, + core2::io::ErrorKind::Interrupted => ErrorKind::Interrupted, + core2::io::ErrorKind::Other => ErrorKind::Other, + core2::io::ErrorKind::UnexpectedEof => ErrorKind::UnexpectedEof, + _ => ErrorKind::Other, + } + } +} + +#[cfg(feature = "use-core2")] +mod core2_impl { + use super::{Read, Write}; + + impl Read for R { + type Error = core2::io::Error; + + fn read(&mut self, buf: &mut [u8]) -> Result { + ::read(self, buf) + } + + fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { + ::read_exact(self, buf) + } + } + + impl Write for W { + type Error = core2::io::Error; + + fn write(&mut self, buf: &[u8]) -> Result { + ::write(self, buf) + } + + fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> { + ::write_all(self, buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + ::flush(self) + } + } +} + +#[cfg(all(not(feature = "use-core2"), not(feature = "std")))] +mod default_impl { + use super::{Read, Write, Error, ErrorKind}; + + impl<'a> Read for &'a [u8] { + type Error = Error; + + fn read(&mut self, buf: &mut [u8]) -> ::core::result::Result { + let amt = ::core::cmp::min(buf.len(), self.len()); + let (a, b) = self.split_at(amt); + + // First check if the amount of bytes we want to read is small: + // `copy_from_slice` will generally expand to a call to `memcpy`, and + // for a single byte the overhead is significant. + if amt == 1 { + buf[0] = a[0]; + } else { + buf[..amt].copy_from_slice(a); + } + + *self = b; + Ok(amt) + } + + fn read_exact(&mut self, buf: &mut [u8]) -> ::core::result::Result<(), Self::Error> { + if buf.len() > self.len() { + return Err( Self::Error { + kind: ErrorKind::UnexpectedEof, + error: alloc::boxed::Box::new(()), + }); + } + let (a, b) = self.split_at(buf.len()); + + // First check if the amount of bytes we want to read is small: + // `copy_from_slice` will generally expand to a call to `memcpy`, and + // for a single byte the overhead is significant. + if buf.len() == 1 { + buf[0] = a[0]; + } else { + buf.copy_from_slice(a); + } + + *self = b; + Ok(()) + } + } + + impl Write for ::alloc::vec::Vec { + type Error = Error; + + fn write(&mut self, buf: &[u8]) -> ::core::result::Result { + self.extend_from_slice(buf); + Ok(buf.len()) + } + + fn write_all(&mut self, buf: &[u8]) -> ::core::result::Result<(), Self::Error> { + self.extend_from_slice(buf); + Ok(()) + } + + fn flush(&mut self) -> ::core::result::Result<(), Self::Error> { + Ok(()) + } + } +} + +#[cfg(test)] +mod tests { + + #[cfg(all(feature = "std", not(feature = "use-core2")))] + 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] + #[cfg(feature = "use-core2-std")] + fn test_core2_write_cursor() { + 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]); + } + + #[test] + fn test_core2_write() { + let mut write_buf = [0u8; 1]; + let mut buf = [10u8; 1]; + (&mut write_buf[..]).write(&mut buf).unwrap(); + assert_eq!(write_buf, [10u8; 1]); + } + } +} diff --git a/src/sha256.rs b/src/sha256.rs index 6c97dbd..0c5b41b 100644 --- a/src/sha256.rs +++ b/src/sha256.rs @@ -237,7 +237,7 @@ macro_rules! round( impl HashEngine { /// Create a new [HashEngine] from a midstate. /// - /// Be aware that this method panics when [length] is + /// Be aware that this method panics when `length` is /// not a multiple of the block size. pub fn from_midstate(midstate: Midstate, length: usize) -> HashEngine { assert!(length % BLOCK_SIZE == 0, "length is no multiple of the block size");