[IO] Provide a macro which implements `io::Write` for types

With the new `bitcoin_io` library, implementing `io::Write`
manually is somewhat tricky - for `std` users we really want to
provide an `std::io::Write` implementation, however for `no-std`
users we want to implement against our internal trait.

Sadly we cannot provide a blanket implementation of
`std::io::Write` for all types whcih implement our `io::Write`
trait as its an out-of-crate impl.

Instead, we provide a macro which will either implement
`std::io::Write` or our `io::Write` depending on the feature flags
set on `bitcoin_io`.
This commit is contained in:
Matt Corallo 2023-09-12 19:32:16 +00:00
parent ac678bb435
commit 7eb5d65bda
3 changed files with 101 additions and 46 deletions

View File

@ -5,65 +5,69 @@
//! Implementations of traits defined in `std` / `core2` and not in `core`. //! Implementations of traits defined in `std` / `core2` and not in `core`.
//! //!
use crate::{hmac, io, ripemd160, sha1, sha256, sha512, siphash24, HashEngine}; use bitcoin_io::impl_write;
impl io::Write for sha1::HashEngine { use crate::{hmac, ripemd160, sha1, sha256, sha512, siphash24, HashEngine};
fn flush(&mut self) -> io::Result<()> { Ok(()) }
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { impl_write!(
self.input(buf); sha1::HashEngine,
|us: &mut sha1::HashEngine, buf| {
us.input(buf);
Ok(buf.len()) Ok(buf.len())
} },
} |_us| { Ok(()) }
);
impl io::Write for sha256::HashEngine { impl_write!(
fn flush(&mut self) -> io::Result<()> { Ok(()) } sha256::HashEngine,
|us: &mut sha256::HashEngine, buf| {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { us.input(buf);
self.input(buf);
Ok(buf.len()) Ok(buf.len())
} },
} |_us| { Ok(()) }
);
impl io::Write for sha512::HashEngine { impl_write!(
fn flush(&mut self) -> io::Result<()> { Ok(()) } sha512::HashEngine,
|us: &mut sha512::HashEngine, buf| {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { us.input(buf);
self.input(buf);
Ok(buf.len()) Ok(buf.len())
} },
} |_us| { Ok(()) }
);
impl io::Write for ripemd160::HashEngine { impl_write!(
fn flush(&mut self) -> io::Result<()> { Ok(()) } ripemd160::HashEngine,
|us: &mut ripemd160::HashEngine, buf| {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { us.input(buf);
self.input(buf);
Ok(buf.len()) Ok(buf.len())
} },
} |_us| { Ok(()) }
);
impl io::Write for siphash24::HashEngine { impl_write!(
fn flush(&mut self) -> io::Result<()> { Ok(()) } siphash24::HashEngine,
|us: &mut siphash24::HashEngine, buf| {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { us.input(buf);
self.input(buf);
Ok(buf.len()) Ok(buf.len())
} },
} |_us| { Ok(()) }
);
impl<T: crate::Hash> io::Write for hmac::HmacEngine<T> { impl_write!(
fn flush(&mut self) -> io::Result<()> { Ok(()) } hmac::HmacEngine<T>,
|us: &mut hmac::HmacEngine<T>, buf| {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { us.input(buf);
self.input(buf);
Ok(buf.len()) Ok(buf.len())
} },
} |_us| { Ok(()) },
T: crate::Hash
);
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::io::Write; use bitcoin_io::io::Write;
use crate::{hash160, hmac, ripemd160, sha1, sha256, sha256d, sha512, siphash24, Hash}; use crate::{hash160, hmac, ripemd160, sha1, sha256, sha256d, sha512, siphash24, Hash};
macro_rules! write_test { macro_rules! write_test {

View File

@ -130,9 +130,6 @@ pub mod siphash24;
use core::{borrow, fmt, hash, ops}; use core::{borrow, fmt, hash, ops};
// You get I/O if you enable "std" or "core2" (as well as during testing).
#[cfg(feature = "bitcoin-io")]
use bitcoin_io::io;
pub use hmac::{Hmac, HmacEngine}; pub use hmac::{Hmac, HmacEngine};
/// A hashing engine which bytes can be serialized into. /// A hashing engine which bytes can be serialized into.

View File

@ -96,3 +96,57 @@ pub mod io {
fn flush(&mut self) -> Result<()> { Ok(()) } fn flush(&mut self) -> Result<()> { Ok(()) }
} }
} }
#[doc(hidden)]
#[cfg(feature = "std")]
/// Re-export std for the below macro
pub use std as _std;
#[macro_export]
/// Because we cannot provide a blanket implementation of [`std::io::Write`] for all implementers
/// of this crate's `io::Write` trait, we provide this macro instead.
///
/// This macro will implement `Write` given a `write` and `flush` fn, either by implementing the
/// crate's native `io::Write` trait directly, or a more generic trait from `std` for users using
/// that feature. In any case, this crate's `io::Write` feature will be implemented for the given
/// type, even if indirectly.
#[cfg(not(feature = "std"))]
macro_rules! impl_write {
($ty: ty, $write_fn: expr, $flush_fn: expr $(, $bounded_ty: ident : $bounds: path),*) => {
impl<$($bounded_ty: $bounds),*> $crate::io::Write for $ty {
#[inline]
fn write(&mut self, buf: &[u8]) -> $crate::io::Result<usize> {
$write_fn(self, buf)
}
#[inline]
fn flush(&mut self) -> $crate::io::Result<()> {
$flush_fn(self)
}
}
}
}
#[macro_export]
/// Because we cannot provide a blanket implementation of [`std::io::Write`] for all implementers
/// of this crate's `io::Write` trait, we provide this macro instead.
///
/// This macro will implement `Write` given a `write` and `flush` fn, either by implementing the
/// crate's native `io::Write` trait directly, or a more generic trait from `std` for users using
/// that feature. In any case, this crate's `io::Write` feature will be implemented for the given
/// type, even if indirectly.
#[cfg(feature = "std")]
macro_rules! impl_write {
($ty: ty, $write_fn: expr, $flush_fn: expr $(, $bounded_ty: ident : $bounds: path),*) => {
impl<$($bounded_ty: $bounds),*> $crate::_std::io::Write for $ty {
#[inline]
fn write(&mut self, buf: &[u8]) -> $crate::_std::io::Result<usize> {
$write_fn(self, buf)
}
#[inline]
fn flush(&mut self) -> $crate::_std::io::Result<()> {
$flush_fn(self)
}
}
}
}