diff options
author | Adam Langley <agl@chromium.org> | 2024-01-05 15:50:16 -0800 |
---|---|---|
committer | Boringssl LUCI CQ <boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2024-01-13 14:26:18 +0000 |
commit | 677414b2f2bbcb2fff5744aa1dbbec555f1f8672 (patch) | |
tree | 20255048bbc26eb143adf88b6fc1721bf3b7d258 /rust | |
parent | ec6a4055437bd384a1dff3842c084eec49e203a8 (diff) | |
download | boringssl-677414b2f2bbcb2fff5744aa1dbbec555f1f8672.zip boringssl-677414b2f2bbcb2fff5744aa1dbbec555f1f8672.tar.gz boringssl-677414b2f2bbcb2fff5744aa1dbbec555f1f8672.tar.bz2 |
Reworking bssl_crypto: digest
Change-Id: I43de22995908ea39b19aa03d167c62a9580ba7b1
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/65168
Reviewed-by: Bob Beck <bbe@google.com>
Commit-Queue: Adam Langley <agl@google.com>
Reviewed-by: Maurice Lam <yukl@google.com>
Diffstat (limited to 'rust')
-rw-r--r-- | rust/bssl-crypto/src/digest.rs | 284 | ||||
-rw-r--r-- | rust/bssl-crypto/src/hkdf.rs | 13 | ||||
-rw-r--r-- | rust/bssl-crypto/src/hmac.rs | 25 | ||||
-rw-r--r-- | rust/bssl-crypto/src/lib.rs | 23 | ||||
-rw-r--r-- | rust/bssl-crypto/src/macros.rs | 105 |
5 files changed, 290 insertions, 160 deletions
diff --git a/rust/bssl-crypto/src/digest.rs b/rust/bssl-crypto/src/digest.rs index 7240297..a10b5ab 100644 --- a/rust/bssl-crypto/src/digest.rs +++ b/rust/bssl-crypto/src/digest.rs @@ -13,162 +13,135 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -use core::marker::PhantomData; +//! Hash functions. +//! +//! ``` +//! use bssl_crypto::digest; +//! +//! // One-shot hashing. +//! let digest: [u8; 32] = digest::Sha256::hash(b"hello"); +//! +//! // Incremental hashing. +//! let mut ctx = digest::Sha256::new(); +//! ctx.update(b"hel"); +//! ctx.update(b"lo"); +//! let digest2: [u8; 32] = ctx.digest(); +//! +//! assert_eq!(digest, digest2); +//! ``` + +use crate::{sealed, FfiSlice, ForeignTypeRef}; +use bssl_sys; -use crate::{CSlice, ForeignTypeRef}; - -/// The SHA-256 digest algorithm. -#[derive(Clone)] -pub struct Sha256 {} - -/// The SHA-512 digest algorithm. -#[derive(Clone)] -pub struct Sha512 {} - -/// A reference to an [`Md`], which abstracts the details of a specific hash function allowing code -/// to deal with the concept of a "hash function" without needing to know exactly which hash function -/// it is. #[non_exhaustive] +#[doc(hidden)] pub struct MdRef; unsafe impl ForeignTypeRef for MdRef { type CType = bssl_sys::EVP_MD; } -/// Used internally to get a BoringSSL internal MD -pub trait Md { - /// The output size of the hash operation. - const OUTPUT_SIZE: usize; +/// Used internally to parameterize other primitives. +pub trait Algorithm { + /// The size of the resulting digest. + const OUTPUT_LEN: usize; /// Gets a reference to a message digest algorithm to be used by the HKDF implementation. - fn get_md() -> &'static MdRef; + #[doc(hidden)] + fn get_md(_: sealed::Sealed) -> &'static MdRef; } -impl Md for Sha256 { - const OUTPUT_SIZE: usize = bssl_sys::SHA256_DIGEST_LENGTH as usize; - - fn get_md() -> &'static MdRef { - // Safety: - // - this always returns a valid pointer to an EVP_MD - unsafe { MdRef::from_ptr(bssl_sys::EVP_sha256() as *mut _) } - } +/// The insecure SHA-1 hash algorithm. +/// +/// Some existing protocols depend on SHA-1 and so it is provided here, but it +/// does not provide collision resistance and should not be used if at all +/// avoidable. Use SHA-256 instead. +#[derive(Clone)] +pub struct InsecureSha1 { + ctx: bssl_sys::SHA_CTX, } -impl Sha256 { - /// Creates a new [Digest] to compute a SHA-256 hash. - pub fn new_digest() -> Digest<Self, { Self::OUTPUT_SIZE }> { - // Note: This cannot be in the trait because using associated constants exprs there - // requires nightly. - Digest::<Self, { Self::OUTPUT_SIZE }>::new() - } +unsafe_iuf_algo!( + InsecureSha1, + 20, + EVP_sha1, + SHA1, + SHA1_Init, + SHA1_Update, + SHA1_Final +); + +/// The SHA-256 hash algorithm. +#[derive(Clone)] +pub struct Sha256 { + ctx: bssl_sys::SHA256_CTX, } -impl Md for Sha512 { - const OUTPUT_SIZE: usize = bssl_sys::SHA512_DIGEST_LENGTH as usize; - - fn get_md() -> &'static MdRef { - // Safety: - // - this always returns a valid pointer to an EVP_MD - unsafe { MdRef::from_ptr(bssl_sys::EVP_sha512() as *mut _) } - } +unsafe_iuf_algo!( + Sha256, + 32, + EVP_sha256, + SHA256, + SHA256_Init, + SHA256_Update, + SHA256_Final +); + +/// The SHA-384 hash algorithm. +#[derive(Clone)] +pub struct Sha384 { + ctx: bssl_sys::SHA512_CTX, } -impl Sha512 { - /// Create a new [Digest] to compute a SHA-512 hash. - pub fn new_digest() -> Digest<Self, { Self::OUTPUT_SIZE }> { - // Note: This cannot be in the trait because using associated constants exprs there - // requires nightly. - Digest::<Self, { Self::OUTPUT_SIZE }>::new() - } +unsafe_iuf_algo!( + Sha384, + 48, + EVP_sha384, + SHA384, + SHA384_Init, + SHA384_Update, + SHA384_Final +); + +/// The SHA-512 hash algorithm. +#[derive(Clone)] +pub struct Sha512 { + ctx: bssl_sys::SHA512_CTX, } -/// A pending digest operation. -pub struct Digest<M: Md, const OUTPUT_SIZE: usize>(bssl_sys::EVP_MD_CTX, PhantomData<M>); - -impl<M: Md, const OUTPUT_SIZE: usize> Digest<M, OUTPUT_SIZE> { - /// Creates a new Digest from the given `Md` type parameter. - /// - /// Panics: - /// - If `Md::OUTPUT_SIZE` is not the same as `OUTPUT_SIZE`. - fn new() -> Self { - // Note: runtime assertion needed here since using {M::OUTPUT_SIZE} in return type requires - // unstable Rust feature. - assert_eq!(M::OUTPUT_SIZE, OUTPUT_SIZE); - let mut md_ctx_uninit = core::mem::MaybeUninit::<bssl_sys::EVP_MD_CTX>::uninit(); - // Safety: - // - `EVP_DigestInit` initializes `md_ctx_uninit` - // - `MdRef` ensures the validity of `md.as_ptr` - let result = - unsafe { bssl_sys::EVP_DigestInit(md_ctx_uninit.as_mut_ptr(), M::get_md().as_ptr()) }; - assert_eq!(result, 1, "bssl_sys::EVP_DigestInit failed"); - // Safety: - // - md_ctx_uninit initialized with EVP_DigestInit, and the function returned 1 (success) - let md_ctx = unsafe { md_ctx_uninit.assume_init() }; - Self(md_ctx, PhantomData) - } - - /// Hashes the provided input into the current digest operation. - pub fn update(&mut self, data: &[u8]) { - let data_ffi = CSlice(data); - // Safety: - // - `data` is a CSlice from safe Rust. - let result = unsafe { - bssl_sys::EVP_DigestUpdate(&mut self.0, data_ffi.as_ptr() as *const _, data_ffi.len()) - }; - assert_eq!(result, 1, "bssl_sys::EVP_DigestUpdate failed"); - } - - /// Computes the final digest value, consuming the object. - #[allow(clippy::expect_used)] - pub fn finalize(mut self) -> [u8; OUTPUT_SIZE] { - let mut digest_uninit = - core::mem::MaybeUninit::<[u8; bssl_sys::EVP_MAX_MD_SIZE as usize]>::uninit(); - let mut len_uninit = core::mem::MaybeUninit::<u32>::uninit(); - // Safety: - // - `digest_uninit` is allocated to `EVP_MAX_MD_SIZE` bytes long, as required by - // EVP_DigestFinal_ex - // - `self.0` is owned by `self`, and is going to be cleaned up on drop. - let result = unsafe { - bssl_sys::EVP_DigestFinal_ex( - &mut self.0, - digest_uninit.as_mut_ptr() as *mut _, - len_uninit.as_mut_ptr(), - ) - }; - assert_eq!(result, 1, "bssl_sys::EVP_DigestFinal_ex failed"); - // Safety: - // - `len_uninit` is initialized by `EVP_DigestFinal_ex`, and we checked the result above - let len = unsafe { len_uninit.assume_init() }; - assert_eq!( - OUTPUT_SIZE, len as usize, - "bssl_sys::EVP_DigestFinal_ex failed" - ); - // Safety: Result of DigestFinal_ex was checked above - let digest = unsafe { digest_uninit.assume_init() }; - digest - .get(..OUTPUT_SIZE) - .and_then(|digest| digest.try_into().ok()) - .expect("The length of `digest` was checked above") - } +unsafe_iuf_algo!( + Sha512, + 64, + EVP_sha512, + SHA512, + SHA512_Init, + SHA512_Update, + SHA512_Final +); + +/// The SHA-512/256 hash algorithm. +#[derive(Clone)] +pub struct Sha512_256 { + ctx: bssl_sys::SHA512_CTX, } -impl<M: Md, const OUTPUT_SIZE: usize> Drop for Digest<M, OUTPUT_SIZE> { - fn drop(&mut self) { - // Safety: `self.0` is owned by `self`, and is invalidated after `drop`. - unsafe { - bssl_sys::EVP_MD_CTX_cleanup(&mut self.0); - } - } -} +unsafe_iuf_algo!( + Sha512_256, + 32, + EVP_sha512_256, + SHA512_256, + SHA512_256_Init, + SHA512_256_Update, + SHA512_256_Final +); #[cfg(test)] mod test { - use crate::test_helpers::decode_hex; - use super::*; + use crate::test_helpers::decode_hex; #[test] - fn test_sha256_c_type() { + fn sha256_c_type() { unsafe { assert_eq!( MdRef::from_ptr(bssl_sys::EVP_sha256() as *mut _).as_ptr(), @@ -178,7 +151,7 @@ mod test { } #[test] - fn test_sha512_c_type() { + fn sha512_c_type() { unsafe { assert_eq!( MdRef::from_ptr(bssl_sys::EVP_sha512() as *mut _).as_ptr(), @@ -188,31 +161,60 @@ mod test { } #[test] - fn test_digest_sha256() { - let mut digest = Sha256::new_digest(); + fn sha1() { + assert_eq!( + decode_hex("a9993e364706816aba3e25717850c26c9cd0d89d"), + InsecureSha1::hash(b"abc") + ); + } + + #[test] + fn sha256() { let msg: [u8; 4] = decode_hex("74ba2521"); - digest.update(&msg); let expected_digest: [u8; 32] = decode_hex("b16aa56be3880d18cd41e68384cf1ec8c17680c45a02b1575dc1518923ae8b0e"); - assert_eq!(expected_digest, digest.finalize()); + + assert_eq!(Sha256::hash(&msg), expected_digest); + + let mut ctx = Sha256::new(); + ctx.update(&msg); + assert_eq!(expected_digest, ctx.digest()); + + let mut ctx = Sha256::new(); + ctx.update(&msg[0..1]); + let mut ctx2 = ctx.clone(); + ctx2.update(&msg[1..]); + assert_eq!(expected_digest, ctx2.digest()); + } + + #[test] + fn sha384() { + assert_eq!( + decode_hex("cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7"), + Sha384::hash(b"abc") + ); } #[test] - fn test_digest_sha512() { - let mut digest = Sha512::new_digest(); + fn sha512() { let msg: [u8; 4] = decode_hex("23be86d5"); - digest.update(&msg); let expected_digest: [u8; 64] = decode_hex(concat!( "76d42c8eadea35a69990c63a762f330614a4699977f058adb988f406fb0be8f2", "ea3dce3a2bbd1d827b70b9b299ae6f9e5058ee97b50bd4922d6d37ddc761f8eb" )); - assert_eq!(expected_digest, digest.finalize()); + + assert_eq!(Sha512::hash(&msg), expected_digest); + + let mut ctx = Sha512::new(); + ctx.update(&msg); + assert_eq!(expected_digest, ctx.digest()); } #[test] - #[should_panic] - fn test_digest_wrong_size() { - // This should not happen since we don't externally expose Digest::new - Digest::<Sha256, 64>::new(); + fn sha512_256() { + assert_eq!( + decode_hex("53048e2681941ef99b2e29b76b4c7dabe4c2d0c634fc6d46e0e2f13107e7af23"), + Sha512_256::hash(b"abc") + ); } } diff --git a/rust/bssl-crypto/src/hkdf.rs b/rust/bssl-crypto/src/hkdf.rs index e4e9c01..f3a68ee 100644 --- a/rust/bssl-crypto/src/hkdf.rs +++ b/rust/bssl-crypto/src/hkdf.rs @@ -12,8 +12,9 @@ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -use crate::digest::Md; +use crate::digest; use crate::digest::{Sha256, Sha512}; +use crate::sealed; use crate::{CSlice, CSliceMut, ForeignTypeRef}; use alloc::vec::Vec; use core::marker::PhantomData; @@ -31,15 +32,15 @@ pub struct InvalidLength; /// Implementation of HKDF operations which are generic over a provided hashing functions. Type /// aliases are provided above for convenience of commonly used hashes -pub struct Hkdf<M: Md> { +pub struct Hkdf<MD: digest::Algorithm> { salt: Option<Vec<u8>>, ikm: Vec<u8>, - _marker: PhantomData<M>, + _marker: PhantomData<MD>, } -impl<M: Md> Hkdf<M> { +impl<MD: digest::Algorithm> Hkdf<MD> { /// The max length of the output key material used for expanding - pub const MAX_OUTPUT_LENGTH: usize = M::OUTPUT_SIZE * 255; + pub const MAX_OUTPUT_LENGTH: usize = MD::OUTPUT_LEN * 255; /// Creates a new instance of HKDF from a salt and key material pub fn new(salt: Option<&[u8]>, ikm: &[u8]) -> Self { @@ -79,7 +80,7 @@ impl<M: Md> Hkdf<M> { bssl_sys::HKDF( okm_cslice.as_mut_ptr(), okm_cslice.len(), - M::get_md().as_ptr(), + MD::get_md(sealed::Sealed).as_ptr(), CSlice::from(self.ikm.as_slice()).as_ptr(), self.ikm.as_slice().len(), CSlice::from(salt).as_ptr(), diff --git a/rust/bssl-crypto/src/hmac.rs b/rust/bssl-crypto/src/hmac.rs index 167e92e..56b06e6 100644 --- a/rust/bssl-crypto/src/hmac.rs +++ b/rust/bssl-crypto/src/hmac.rs @@ -13,8 +13,9 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ use crate::{ - digest::{Md, Sha256, Sha512}, - CSlice, ForeignTypeRef as _, + digest, + digest::{Sha256, Sha512}, + sealed, CSlice, ForeignTypeRef as _, }; use core::{ ffi::{c_uint, c_void}, @@ -154,11 +155,11 @@ pub struct MacError; /// Private generically implemented function for computing hmac as a oneshot operation. /// This should only be exposed publicly by types with the correct output size `N` which corresponds -/// to the output size of the provided generic hash function. Ideally `N` would just come from `M`, +/// to the output size of the provided generic hash function. Ideally `N` would just come from `MD`, /// but this is not possible until the Rust language can support the `min_const_generics` feature. /// Until then we will have to pass both separately: https://github.com/rust-lang/rust/issues/60551 #[inline] -fn hmac<const N: usize, M: Md>(key: &[u8], data: &[u8]) -> [u8; N] { +fn hmac<const N: usize, MD: digest::Algorithm>(key: &[u8], data: &[u8]) -> [u8; N] { let mut out = [0_u8; N]; let mut size: c_uint = 0; @@ -167,7 +168,7 @@ fn hmac<const N: usize, M: Md>(key: &[u8], data: &[u8]) -> [u8; N] { // - If NULL is returned on error we panic immediately let result = unsafe { bssl_sys::HMAC( - M::get_md().as_ptr(), + MD::get_md(sealed::Sealed).as_ptr(), CSlice::from(key).as_ptr(), key.len(), CSlice::from(data).as_ptr(), @@ -184,15 +185,15 @@ fn hmac<const N: usize, M: Md>(key: &[u8], data: &[u8]) -> [u8; N] { /// Private generically implemented hmac instance given a generic hash function and a length `N`, /// where `N` is the output size of the hash function. This should only be exposed publicly by /// wrapper types with the correct output size `N` which corresponds to the output size of the -/// provided generic hash function. Ideally `N` would just come from `M`, but this is not possible +/// provided generic hash function. Ideally `N` would just come from `MD`, but this is not possible /// until the Rust language can support the `min_const_generics` feature. Until then we will have to /// pass both separately: https://github.com/rust-lang/rust/issues/60551 -struct Hmac<const N: usize, M: Md> { +struct Hmac<const N: usize, MD: digest::Algorithm> { ctx: *mut bssl_sys::HMAC_CTX, - _marker: PhantomData<M>, + _marker: PhantomData<MD>, } -impl<const N: usize, M: Md> Hmac<N, M> { +impl<const N: usize, MD: digest::Algorithm> Hmac<N, MD> { /// Creates a new HMAC operation from a fixed-length key. fn new(key: [u8; N]) -> Self { Self::new_from_slice(&key) @@ -219,7 +220,7 @@ impl<const N: usize, M: Md> Hmac<N, M> { ctx, CSlice::from(key).as_ptr() as *const c_void, key.len(), - M::get_md().as_ptr(), + MD::get_md(sealed::Sealed).as_ptr(), ptr::null_mut(), ) }; @@ -311,7 +312,7 @@ impl<const N: usize, M: Md> Hmac<N, M> { self.ctx, ptr::null_mut(), 0, - M::get_md().as_ptr(), + MD::get_md(sealed::Sealed).as_ptr(), ptr::null_mut(), ) }; @@ -319,7 +320,7 @@ impl<const N: usize, M: Md> Hmac<N, M> { } } -impl<const N: usize, M: Md> Drop for Hmac<N, M> { +impl<const N: usize, MD: digest::Algorithm> Drop for Hmac<N, MD> { fn drop(&mut self) { unsafe { bssl_sys::HMAC_CTX_free(self.ctx) } } diff --git a/rust/bssl-crypto/src/lib.rs b/rust/bssl-crypto/src/lib.rs index 022f5a3..dea522f 100644 --- a/rust/bssl-crypto/src/lib.rs +++ b/rust/bssl-crypto/src/lib.rs @@ -30,6 +30,9 @@ extern crate core; use core::ffi::c_void; +#[macro_use] +mod macros; + /// Authenticated Encryption with Additional Data algorithms. pub mod aead; @@ -39,7 +42,6 @@ pub mod aes; /// Ciphers. pub mod cipher; -/// Hash functions. pub mod digest; /// Ed25519, a signature scheme. @@ -247,6 +249,20 @@ unsafe trait ForeignType { fn as_ptr(&self) -> *mut Self::CType; } +/// Returns a BoringSSL structure that is initialized by some function. +/// Requires that the given function completely initializes the value. +/// +/// (Tagged `unsafe` because a no-op argument would otherwise expose +/// uninitialized memory.) +unsafe fn initialized_struct<T, F>(init: F) -> T +where + F: FnOnce(*mut T), +{ + let mut out_uninit = core::mem::MaybeUninit::<T>::uninit(); + init(out_uninit.as_mut_ptr()); + unsafe { out_uninit.assume_init() } +} + /// Wrap a closure that initializes an output buffer and return that buffer as /// an array. Requires that the closure fully initialize the given buffer. /// @@ -290,3 +306,8 @@ where None } } + +/// Used to prevent external implementations of internal traits. +mod sealed { + pub struct Sealed; +} diff --git a/rust/bssl-crypto/src/macros.rs b/rust/bssl-crypto/src/macros.rs new file mode 100644 index 0000000..79049d4 --- /dev/null +++ b/rust/bssl-crypto/src/macros.rs @@ -0,0 +1,105 @@ +/* Copyright (c) 2024, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +// Generates a hash function from init/update/final-style FFI functions. Rust +// doesn't accept function pointers as a generic arguments so this is the only +// mechanism to avoid duplicating the code. +// +// The name is prefixed with "unsafe_" because it contains unsafe blocks. +// +// Safety: see the "Safety" sections within about the requirements for the +// functions named in the macro parameters. +macro_rules! unsafe_iuf_algo { + ($name:ident, $output_len:expr, $evp_md:ident, $one_shot:ident, $init:ident, $update:ident, $final_func:ident) => { + impl Algorithm for $name { + const OUTPUT_LEN: usize = $output_len as usize; + + fn get_md(_: sealed::Sealed) -> &'static MdRef { + // Safety: + // - this always returns a valid pointer to an EVP_MD. + unsafe { MdRef::from_ptr(bssl_sys::$evp_md() as *mut _) } + } + } + + impl $name { + /// Digest `input` in a single operation. + pub fn hash(input: &[u8]) -> [u8; $output_len] { + // Safety: it is assumed that `$one_shot` indeed writes + // `$output_len` bytes. + unsafe { + crate::with_output_array(|out, _| { + bssl_sys::$one_shot(input.as_ffi_ptr(), input.len(), out); + }) + } + } + + /// Create a new context for incremental hashing. + pub fn new() -> Self { + unsafe { + Self { + ctx: crate::initialized_struct(|ctx| { + // Safety: type checking will ensure that `ctx` is the + // correct type for `$init` to write into. + bssl_sys::$init(ctx); + }), + } + } + } + + /// Hash the contents of `input`. + pub fn update(&mut self, input: &[u8]) { + // Safety: arguments point to a valid buffer. + unsafe { + bssl_sys::$update(&mut self.ctx, input.as_ffi_void_ptr(), input.len()); + } + } + + /// Finish the hashing and return the digest. + pub fn digest(mut self) -> [u8; $output_len] { + // Safety: it is assumed that `$final_func` indeed writes + // `$output_len` bytes. + unsafe { + crate::with_output_array(|out, _| { + bssl_sys::$final_func(out, &mut self.ctx); + }) + } + } + } + + impl From<$name> for [u8; $output_len] { + fn from(ctx: $name) -> [u8; $output_len] { + ctx.digest() + } + } + + impl From<$name> for alloc::vec::Vec<u8> { + fn from(ctx: $name) -> alloc::vec::Vec<u8> { + ctx.digest().into() + } + } + + #[cfg(feature = "std")] + impl std::io::Write for $name { + fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { + self.update(buf); + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } + } + }; +} |