Merge rust-bitcoin/rust-bitcoin#2066: Add a `bitcoin-io` crate

add371d263 Remove `core2` dependency entirely (Matt Corallo)
b7dd16da99 [IO] Use our own io::Error type (Matt Corallo)
c95b59327a Explicitly use `std::io::Error` when implementing `std` traits (Matt Corallo)
9e1cd372cb Use `io::Error::get_ref()` over `std::error::Error::source()` (Matt Corallo)
3caaadf9bb [IO] Replace the `io::Cursor` re-export with our own `Cursor` (Matt Corallo)
141343edb4 [IO] Move to custom `Read` trait mirroring `std::io::Read` (Matt Corallo)
7395093f94 Stop relying on `Take`'s `by_ref` method (Matt Corallo)
2364e1a877 Stop relying on blanket Read impl for all &mut Read (Matt Corallo)
6aa7ccf841 [IO] Replace `std::io::Sink` usage with our own trivial impl (Matt Corallo)
7eb5d65bda [IO] Provide a macro which implements `io::Write` for types (Matt Corallo)
ac678bb435 [IO] Move to custom `Write` trait mirroring `std::io::Write` (Matt Corallo)
5f2395ce56 Add missing `?Sized` bounds to `io::Write` parameters (Matt Corallo)
2348449d2a Stop relying on `std::io::Write`'s `&mut Write` blanket impl (Matt Corallo)
5e0209569c Use `io::sink` rather than our custom `EmptyWrite` utility (Matt Corallo)
a0ade883b6 [IO] Move io module into selected re-exports (Matt Corallo)
27c7c4e26a Add a `bitcoin_io` crate (Matt Corallo)

Pull request description:

  In order to support standard (de)serialization of structs, the
  `rust-bitcoin` ecosystem uses the standard `std::io::{Read,Write}`
  traits. This works great for environments with `std`, however sadly
  the `std::io` module has not yet been added to the `core` crate.

  Thus, in `no-std`, the `rust-bitcoin` ecosystem has historically
  used the `core2` crate to provide copies of the `std::io` module
  without any major dependencies. Sadly, its one dependency,
  `memchr`, recently broke our MSRV.

  Worse, because we didn't want to take on any excess dependencies
  for `std` builds, `rust-bitcoin` has had to have
  mutually-exclusive `std` and `no-std` builds. This breaks general
  assumptions about how features work in Rust, causing substantial
  pain for applications far downstream of `rust-bitcoin` crates.

  This is mostly done, I'm still finalizing the `io::Error` commit at the end to drop the `core2` required dep in no-std, but its getting there. Would love further feedback on the approach or code-level review on these first handful of commits.

ACKs for top commit:
  tcharding:
    ACK add371d263
  apoelstra:
    ACK add371d263
  Kixunil:
    ACK add371d263

Tree-SHA512: 18698ea8b1b65108ee0f695d5062d2562c8df2f50bf85d93442648da3b35a4184a5d5d2a493aed0adaadc83f663f0cd2ac735c34941cc9a6fa58d826e548e091
This commit is contained in:
Andrew Poelstra 2023-11-19 14:03:25 +00:00
commit 675da34127
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
29 changed files with 623 additions and 249 deletions

View File

@ -38,9 +38,9 @@ dependencies = [
"bech32",
"bincode",
"bitcoin-internals",
"bitcoin-io",
"bitcoin_hashes",
"bitcoinconsensus",
"core2",
"hex-conservative",
"hex_lit",
"mutagen",
@ -69,11 +69,15 @@ dependencies = [
"serde",
]
[[package]]
name = "bitcoin-io"
version = "0.1.0"
[[package]]
name = "bitcoin_hashes"
version = "0.13.0"
dependencies = [
"core2",
"bitcoin-io",
"hex-conservative",
"schemars",
"serde",
@ -109,15 +113,6 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
[[package]]
name = "core2"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cf12d2dad3ed124aa116f59561428478993d69ab81ae4d30e5349c9c5b5a5f6"
dependencies = [
"memchr",
]
[[package]]
name = "dyn-clone"
version = "1.0.0"
@ -146,9 +141,6 @@ name = "hex-conservative"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2"
dependencies = [
"core2",
]
[[package]]
name = "hex_lit"
@ -191,12 +183,6 @@ version = "0.2.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74dfca3d9957906e8d1e6a0b641dc9a59848e793f1da2165889fd4f62d10d79c"
[[package]]
name = "memchr"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e01e64d9017d18e7fc09d8e4fe0e28ff6931019e979fb8019319db7ca827f8a6"
[[package]]
name = "memmap2"
version = "0.5.10"

View File

@ -37,9 +37,9 @@ dependencies = [
"bech32",
"bincode",
"bitcoin-internals",
"bitcoin-io",
"bitcoin_hashes",
"bitcoinconsensus",
"core2",
"hex-conservative",
"hex_lit",
"mutagen",
@ -68,11 +68,15 @@ dependencies = [
"serde",
]
[[package]]
name = "bitcoin-io"
version = "0.1.0"
[[package]]
name = "bitcoin_hashes"
version = "0.13.0"
dependencies = [
"core2",
"bitcoin-io",
"hex-conservative",
"schemars",
"serde",
@ -108,15 +112,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "core2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "239fa3ae9b63c2dc74bd3fa852d4792b8b305ae64eeede946265b6af62f1fff3"
dependencies = [
"memchr",
]
[[package]]
name = "dyn-clone"
version = "1.0.11"
@ -145,9 +140,6 @@ name = "hex-conservative"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2"
dependencies = [
"core2",
]
[[package]]
name = "hex_lit"
@ -190,12 +182,6 @@ version = "0.2.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memmap2"
version = "0.5.10"

View File

@ -1,5 +1,5 @@
[workspace]
members = ["bitcoin", "hashes", "internals", "fuzz"]
members = ["bitcoin", "hashes", "internals", "fuzz", "io"]
[patch.crates-io.bitcoin]
path = "bitcoin"
@ -9,3 +9,6 @@ path = "hashes"
[patch.crates-io.bitcoin-internals]
path = "internals"
[patch.crates-io.bitcoin-io]
path = "io"

View File

@ -26,8 +26,8 @@ bitcoinconsensus-std = ["bitcoinconsensus/std", "std"]
# The no-std feature doesn't disable std - you need to turn off the std feature for that by disabling default.
# Instead no-std enables additional features required for this crate to be usable without std.
# As a result, both can be enabled without conflict.
std = ["secp256k1/std", "hashes/std", "bech32/std", "internals/std", "hex/std"]
no-std = ["core2", "hashes/alloc", "hashes/core2", "bech32/alloc", "secp256k1/alloc", "hex/alloc", "hex/core2"]
std = ["secp256k1/std", "bitcoin-io/std", "hashes/std", "bech32/std", "internals/std", "hex/std"]
no-std = ["hashes/alloc", "hashes/io", "bitcoin-io/alloc", "bech32/alloc", "secp256k1/alloc", "hex/alloc"]
[package.metadata.docs.rs]
all-features = true
@ -41,12 +41,12 @@ hashes = { package = "bitcoin_hashes", version = "0.13.0", default-features = fa
secp256k1 = { version = "0.28.0", default-features = false, features = ["hashes"] }
hex_lit = "0.1.1"
bitcoin-io = { version = "0.1", default-features = false }
base64 = { version = "0.21.3", optional = true }
# Only use this feature for no-std builds, otherwise use bitcoinconsensus-std.
bitcoinconsensus = { version = "0.20.2-0.5.0", default-features = false, optional = true }
# There is no reason to use this dependency directly, it is activated by the "no-std" feature.
core2 = { version = "0.3.2", default-features = false, features = ["alloc"], optional = true }
# Do NOT use this as a feature! Use the `serde` feature instead.
actual-serde = { package = "serde", version = "1.0.103", default-features = false, features = [ "derive", "alloc" ], optional = true }

View File

@ -17,7 +17,6 @@ panic-halt = "0.2.0"
alloc-cortex-m = "0.4.1"
bitcoin = { path="../", default-features = false, features = ["no-std", "secp-lowmemory"] }
[[bin]]
name = "embedded"
test = false
@ -33,3 +32,6 @@ path = "../../hashes"
[patch.crates-io.bitcoin-internals]
path = "../../internals"
[patch.crates-io.bitcoin-io]
path = "../../io"

View File

@ -271,9 +271,7 @@ impl GcsFilterReader {
I::Item: Borrow<[u8]>,
R: io::Read + ?Sized,
{
let mut decoder = reader;
let n_elements: VarInt = Decodable::consensus_decode(&mut decoder).unwrap_or(VarInt(0));
let reader = &mut decoder;
let n_elements: VarInt = Decodable::consensus_decode(reader).unwrap_or(VarInt(0));
// map hashes to [0, n_elements << grp]
let nm = n_elements.0 * self.m;
let mut mapped =
@ -316,9 +314,7 @@ impl GcsFilterReader {
I::Item: Borrow<[u8]>,
R: io::Read + ?Sized,
{
let mut decoder = reader;
let n_elements: VarInt = Decodable::consensus_decode(&mut decoder).unwrap_or(VarInt(0));
let reader = &mut decoder;
let n_elements: VarInt = Decodable::consensus_decode(reader).unwrap_or(VarInt(0));
// map hashes to [0, n_elements << grp]
let nm = n_elements.0 * self.m;
let mut mapped =
@ -393,7 +389,7 @@ impl<'a, W: io::Write> GcsFilterWriter<'a, W> {
mapped.sort_unstable();
// write number of elements as varint
let mut wrote = VarInt::from(mapped.len()).consensus_encode(&mut self.writer)?;
let mut wrote = VarInt::from(mapped.len()).consensus_encode(self.writer)?;
// write out deltas of sorted values into a Golonb-Rice coded bit stream
let mut writer = BitStreamWriter::new(self.writer);
@ -442,7 +438,7 @@ impl GcsFilter {
/// Golomb-Rice decodes a number from a bit stream (parameter 2^k).
fn golomb_rice_decode<R>(&self, reader: &mut BitStreamReader<R>) -> Result<u64, io::Error>
where
R: io::Read,
R: io::Read + ?Sized,
{
let mut q = 0u64;
while reader.read(1)? == 1 {
@ -459,13 +455,13 @@ impl GcsFilter {
}
/// Bitwise stream reader.
pub struct BitStreamReader<'a, R> {
pub struct BitStreamReader<'a, R: ?Sized> {
buffer: [u8; 1],
offset: u8,
reader: &'a mut R,
}
impl<'a, R: io::Read> BitStreamReader<'a, R> {
impl<'a, R: io::Read + ?Sized> BitStreamReader<'a, R> {
/// Creates a new [`BitStreamReader`] that reads bitwise from a given `reader`.
pub fn new(reader: &'a mut R) -> BitStreamReader<'a, R> {
BitStreamReader { buffer: [0u8], reader, offset: 8 }

View File

@ -647,7 +647,7 @@ mod benches {
use super::Block;
use crate::consensus::{deserialize, Decodable, Encodable};
use crate::EmptyWrite;
use crate::io::sink;
#[bench]
pub fn bench_stream_reader(bh: &mut Bencher) {
@ -684,7 +684,7 @@ mod benches {
let block: Block = deserialize(&raw_block[..]).unwrap();
bh.iter(|| {
let size = block.consensus_encode(&mut EmptyWrite);
let size = block.consensus_encode(&mut sink());
black_box(&size);
});
}

View File

@ -2156,7 +2156,7 @@ mod benches {
use super::Transaction;
use crate::consensus::{deserialize, Encodable};
use crate::EmptyWrite;
use crate::io::sink;
const SOME_TX: &str = "0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000";
@ -2191,7 +2191,7 @@ mod benches {
let tx: Transaction = deserialize(&raw_tx).unwrap();
bh.iter(|| {
let size = tx.consensus_encode(&mut EmptyWrite);
let size = tx.consensus_encode(&mut sink());
black_box(&size);
});
}

View File

@ -320,7 +320,7 @@ pub trait Decodable: Sized {
/// instead.
#[inline]
fn consensus_decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
Self::consensus_decode_from_finite_reader(reader.take(MAX_VEC_SIZE as u64).by_ref())
Self::consensus_decode_from_finite_reader(&mut reader.take(MAX_VEC_SIZE as u64))
}
}
@ -643,11 +643,11 @@ impl_vec!((u32, Address));
#[cfg(feature = "std")]
impl_vec!(AddrV2Message);
pub(crate) fn consensus_encode_with_size<W: io::Write>(
pub(crate) fn consensus_encode_with_size<W: io::Write + ?Sized>(
data: &[u8],
mut w: W,
w: &mut W,
) -> Result<usize, io::Error> {
let vi_len = VarInt(data.len() as u64).consensus_encode(&mut w)?;
let vi_len = VarInt(data.len() as u64).consensus_encode(w)?;
w.emit_slice(data)?;
Ok(vi_len + data.len())
}
@ -662,8 +662,8 @@ struct ReadBytesFromFiniteReaderOpts {
/// This function relies on reader being bound in amount of data
/// it returns for OOM protection. See [`Decodable::consensus_decode_from_finite_reader`].
#[inline]
fn read_bytes_from_finite_reader<D: io::Read>(
mut d: D,
fn read_bytes_from_finite_reader<D: io::Read + ?Sized>(
d: &mut D,
mut opts: ReadBytesFromFiniteReaderOpts,
) -> Result<Vec<u8>, Error> {
let mut ret = vec![];
@ -1234,7 +1234,7 @@ mod tests {
for chunk_size in 1..20 {
assert_eq!(
read_bytes_from_finite_reader(
io::Cursor::new(&data),
&mut io::Cursor::new(&data),
ReadBytesFromFiniteReaderOpts { len: data.len(), chunk_size }
)
.unwrap(),

View File

@ -174,10 +174,8 @@ impl<'a, T: 'a + Encodable, E: ByteEncoder> fmt::Display for DisplayWrapper<'a,
self.0.consensus_encode(&mut writer).map_err(|error| {
#[cfg(debug_assertions)]
{
use crate::StdError;
if error.kind() != io::ErrorKind::Other
|| error.source().is_some()
|| error.get_ref().is_some()
|| !writer.writer.was_error
{
panic!(
@ -429,8 +427,6 @@ impl<E: fmt::Debug, I: Iterator<Item = Result<u8, E>>> IterReader<E, I> {
fn new(iterator: I) -> Self { IterReader { iterator: iterator.fuse(), error: None } }
fn decode<T: Decodable>(mut self) -> Result<T, DecodeError<E>> {
use crate::StdError;
let result = T::consensus_decode(&mut self);
match (result, self.error) {
(Ok(_), None) if self.iterator.next().is_some() => {
@ -438,7 +434,7 @@ impl<E: fmt::Debug, I: Iterator<Item = Result<u8, E>>> IterReader<E, I> {
},
(Ok(value), None) => Ok(value),
(Ok(_), Some(error)) => panic!("{} silently ate the error: {:?}", core::any::type_name::<T>(), error),
(Err(ConsensusError::Io(io_error)), Some(de_error)) if io_error.kind() == io::ErrorKind::Other && io_error.source().is_none() => Err(DecodeError::Other(de_error)),
(Err(ConsensusError::Io(io_error)), Some(de_error)) if io_error.kind() == io::ErrorKind::Other && io_error.get_ref().is_none() => Err(DecodeError::Other(de_error)),
(Err(consensus_error), None) => Err(DecodeError::Consensus(consensus_error)),
(Err(ConsensusError::Io(io_error)), de_error) => panic!("Unexpected IO error {:?} returned from {}::consensus_decode(), deserialization error: {:?}", io_error, core::any::type_name::<T>(), de_error),
(Err(consensus_error), Some(de_error)) => panic!("{} should've returned `Other` IO error because of deserialization error {:?} but it returned consensus error {:?} instead", core::any::type_name::<T>(), de_error, consensus_error),
@ -494,8 +490,6 @@ impl<E> With<E> {
if serializer.is_human_readable() {
serializer.collect_str(&DisplayWrapper::<'_, _, E>(value, Default::default()))
} else {
use crate::StdError;
let serializer = serializer.serialize_seq(None)?;
let mut writer = BinWriter { serializer, error: None };
@ -505,7 +499,7 @@ impl<E> With<E> {
(Ok(_), Some(error)) =>
panic!("{} silently ate an IO error: {:?}", core::any::type_name::<T>(), error),
(Err(io_error), Some(ser_error))
if io_error.kind() == io::ErrorKind::Other && io_error.source().is_none() =>
if io_error.kind() == io::ErrorKind::Other && io_error.get_ref().is_none() =>
Err(ser_error),
(Err(io_error), ser_error) => panic!(
"{} returned an unexpected IO error: {:?} serialization error: {:?}",

View File

@ -71,7 +71,7 @@ impl PublicKey {
}
/// Write the public key into a writer
pub fn write_into<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
pub fn write_into<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<(), io::Error> {
self.with_serialized(|bytes| writer.write_all(bytes))
}
@ -79,7 +79,7 @@ impl PublicKey {
///
/// This internally reads the first byte before reading the rest, so
/// use of a `BufReader` is recommended.
pub fn read_from<R: io::Read>(mut reader: R) -> Result<Self, io::Error> {
pub fn read_from<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, io::Error> {
let mut bytes = [0; 65];
reader.read_exact(&mut bytes[0..1])?;
@ -87,7 +87,7 @@ impl PublicKey {
reader.read_exact(&mut bytes[1..])?;
Self::from_slice(bytes).map_err(|e| {
// Need a static string for core2
// Need a static string for no-std io
#[cfg(feature = "std")]
let reason = e;
#[cfg(not(feature = "std"))]
@ -755,7 +755,6 @@ mod tests {
use super::*;
use crate::address::Address;
use crate::io;
use crate::network::Network::{Bitcoin, Testnet};
#[test]
@ -917,11 +916,11 @@ mod tests {
// sanity checks
assert!(PublicKey::read_from(&mut cursor).is_err());
assert!(PublicKey::read_from(io::Cursor::new(&[])).is_err());
assert!(PublicKey::read_from(io::Cursor::new(&[0; 33][..])).is_err());
assert!(PublicKey::read_from(io::Cursor::new(&[2; 32][..])).is_err());
assert!(PublicKey::read_from(io::Cursor::new(&[0; 65][..])).is_err());
assert!(PublicKey::read_from(io::Cursor::new(&[4; 64][..])).is_err());
assert!(PublicKey::read_from(&mut io::Cursor::new(&[])).is_err());
assert!(PublicKey::read_from(&mut io::Cursor::new(&[0; 33][..])).is_err());
assert!(PublicKey::read_from(&mut io::Cursor::new(&[2; 32][..])).is_err());
assert!(PublicKey::read_from(&mut io::Cursor::new(&[0; 65][..])).is_err());
assert!(PublicKey::read_from(&mut io::Cursor::new(&[4; 64][..])).is_err());
}
#[test]

View File

@ -673,9 +673,9 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
/// Encodes the BIP341 signing data for any flag type into a given object implementing a
/// [`io::Write`] trait.
pub fn taproot_encode_signing_data_to<Write: io::Write, T: Borrow<TxOut>>(
pub fn taproot_encode_signing_data_to<Write: io::Write + ?Sized, T: Borrow<TxOut>>(
&mut self,
mut writer: Write,
writer: &mut Write,
input_index: usize,
prevouts: &Prevouts<T>,
annex: Option<Annex>,
@ -687,18 +687,18 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
let (sighash, anyone_can_pay) = sighash_type.split_anyonecanpay_flag();
// epoch
0u8.consensus_encode(&mut writer)?;
0u8.consensus_encode(writer)?;
// * Control:
// hash_type (1).
(sighash_type as u8).consensus_encode(&mut writer)?;
(sighash_type as u8).consensus_encode(writer)?;
// * Transaction Data:
// nVersion (4): the nVersion of the transaction.
self.tx.borrow().version.consensus_encode(&mut writer)?;
self.tx.borrow().version.consensus_encode(writer)?;
// nLockTime (4): the nLockTime of the transaction.
self.tx.borrow().lock_time.consensus_encode(&mut writer)?;
self.tx.borrow().lock_time.consensus_encode(writer)?;
// If the hash_type & 0x80 does not equal SIGHASH_ANYONECANPAY:
// sha_prevouts (32): the SHA256 of the serialization of all input outpoints.
@ -706,16 +706,16 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
// sha_scriptpubkeys (32): the SHA256 of the serialization of all spent output scriptPubKeys.
// sha_sequences (32): the SHA256 of the serialization of all input nSequence.
if !anyone_can_pay {
self.common_cache().prevouts.consensus_encode(&mut writer)?;
self.taproot_cache(prevouts.get_all()?).amounts.consensus_encode(&mut writer)?;
self.taproot_cache(prevouts.get_all()?).script_pubkeys.consensus_encode(&mut writer)?;
self.common_cache().sequences.consensus_encode(&mut writer)?;
self.common_cache().prevouts.consensus_encode(writer)?;
self.taproot_cache(prevouts.get_all()?).amounts.consensus_encode(writer)?;
self.taproot_cache(prevouts.get_all()?).script_pubkeys.consensus_encode(writer)?;
self.common_cache().sequences.consensus_encode(writer)?;
}
// If hash_type & 3 does not equal SIGHASH_NONE or SIGHASH_SINGLE:
// sha_outputs (32): the SHA256 of the serialization of all outputs in CTxOut format.
if sighash != TapSighashType::None && sighash != TapSighashType::Single {
self.common_cache().outputs.consensus_encode(&mut writer)?;
self.common_cache().outputs.consensus_encode(writer)?;
}
// * Data about this input:
@ -728,7 +728,7 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
if leaf_hash_code_separator.is_some() {
spend_type |= 2u8;
}
spend_type.consensus_encode(&mut writer)?;
spend_type.consensus_encode(writer)?;
// If hash_type & 0x80 equals SIGHASH_ANYONECANPAY:
// outpoint (36): the COutPoint of this input (32-byte hash + 4-byte little-endian).
@ -742,12 +742,12 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
inputs_size: self.tx.borrow().input.len(),
})?;
let previous_output = prevouts.get(input_index)?;
txin.previous_output.consensus_encode(&mut writer)?;
previous_output.value.consensus_encode(&mut writer)?;
previous_output.script_pubkey.consensus_encode(&mut writer)?;
txin.sequence.consensus_encode(&mut writer)?;
txin.previous_output.consensus_encode(writer)?;
previous_output.value.consensus_encode(writer)?;
previous_output.script_pubkey.consensus_encode(writer)?;
txin.sequence.consensus_encode(writer)?;
} else {
(input_index as u32).consensus_encode(&mut writer)?;
(input_index as u32).consensus_encode(writer)?;
}
// If an annex is present (the lowest bit of spend_type is set):
@ -757,7 +757,7 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
let mut enc = sha256::Hash::engine();
annex.consensus_encode(&mut enc)?;
let hash = sha256::Hash::from_engine(enc);
hash.consensus_encode(&mut writer)?;
hash.consensus_encode(writer)?;
}
// * Data about this output:
@ -775,7 +775,7 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
})?
.consensus_encode(&mut enc)?;
let hash = sha256::Hash::from_engine(enc);
hash.consensus_encode(&mut writer)?;
hash.consensus_encode(writer)?;
}
// if (scriptpath):
@ -783,9 +783,9 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
// ss += bytes([0])
// ss += struct.pack("<i", codeseparator_pos)
if let Some((hash, code_separator_pos)) = leaf_hash_code_separator {
hash.as_byte_array().consensus_encode(&mut writer)?;
KEY_VERSION_0.consensus_encode(&mut writer)?;
code_separator_pos.consensus_encode(&mut writer)?;
hash.as_byte_array().consensus_encode(writer)?;
KEY_VERSION_0.consensus_encode(writer)?;
code_separator_pos.consensus_encode(writer)?;
}
Ok(())
@ -860,9 +860,9 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
/// `script_code` is dependent on the type of the spend transaction. For p2wpkh use
/// [`Script::p2wpkh_script_code`], for p2wsh just pass in the witness script. (Also see
/// [`Self::p2wpkh_signature_hash`] and [`SighashCache::p2wsh_signature_hash`].)
pub fn segwit_v0_encode_signing_data_to<Write: io::Write>(
pub fn segwit_v0_encode_signing_data_to<Write: io::Write + ?Sized>(
&mut self,
mut writer: Write,
writer: &mut Write,
input_index: usize,
script_code: &Script,
value: Amount,
@ -872,21 +872,21 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
let (sighash, anyone_can_pay) = sighash_type.split_anyonecanpay_flag();
self.tx.borrow().version.consensus_encode(&mut writer)?;
self.tx.borrow().version.consensus_encode(writer)?;
if !anyone_can_pay {
self.segwit_cache().prevouts.consensus_encode(&mut writer)?;
self.segwit_cache().prevouts.consensus_encode(writer)?;
} else {
zero_hash.consensus_encode(&mut writer)?;
zero_hash.consensus_encode(writer)?;
}
if !anyone_can_pay
&& sighash != EcdsaSighashType::Single
&& sighash != EcdsaSighashType::None
{
self.segwit_cache().sequences.consensus_encode(&mut writer)?;
self.segwit_cache().sequences.consensus_encode(writer)?;
} else {
zero_hash.consensus_encode(&mut writer)?;
zero_hash.consensus_encode(writer)?;
}
{
@ -896,14 +896,14 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
inputs_size: self.tx.borrow().input.len(),
})?;
txin.previous_output.consensus_encode(&mut writer)?;
script_code.consensus_encode(&mut writer)?;
value.consensus_encode(&mut writer)?;
txin.sequence.consensus_encode(&mut writer)?;
txin.previous_output.consensus_encode(writer)?;
script_code.consensus_encode(writer)?;
value.consensus_encode(writer)?;
txin.sequence.consensus_encode(writer)?;
}
if sighash != EcdsaSighashType::Single && sighash != EcdsaSighashType::None {
self.segwit_cache().outputs.consensus_encode(&mut writer)?;
self.segwit_cache().outputs.consensus_encode(writer)?;
} else if sighash == EcdsaSighashType::Single && input_index < self.tx.borrow().output.len()
{
let mut single_enc = LegacySighash::engine();
@ -914,8 +914,8 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
writer.write_all(&zero_hash[..])?;
}
self.tx.borrow().lock_time.consensus_encode(&mut writer)?;
sighash_type.to_u32().consensus_encode(&mut writer)?;
self.tx.borrow().lock_time.consensus_encode(writer)?;
sighash_type.to_u32().consensus_encode(writer)?;
Ok(())
}
@ -984,9 +984,9 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
///
/// This function can't handle the SIGHASH_SINGLE bug internally, so it returns [`EncodeSigningDataResult`]
/// that must be handled by the caller (see [`EncodeSigningDataResult::is_sighash_single_bug`]).
pub fn legacy_encode_signing_data_to<Write: io::Write, U: Into<u32>>(
pub fn legacy_encode_signing_data_to<Write: io::Write + ?Sized, U: Into<u32>>(
&self,
writer: Write,
writer: &mut Write,
input_index: usize,
script_pubkey: &Script,
sighash_type: U,
@ -1011,9 +1011,9 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
return EncodeSigningDataResult::SighashSingleBug;
}
fn encode_signing_data_to_inner<Write: io::Write>(
fn encode_signing_data_to_inner<Write: io::Write + ?Sized>(
self_: &Transaction,
mut writer: Write,
writer: &mut Write,
input_index: usize,
script_pubkey: &Script,
sighash_type: u32,
@ -1074,8 +1074,8 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
_ => unreachable!(),
};
// hash the result
tx.consensus_encode(&mut writer)?;
sighash_type.to_le_bytes().consensus_encode(&mut writer)?;
tx.consensus_encode(writer)?;
sighash_type.to_le_bytes().consensus_encode(writer)?;
Ok(())
}

View File

@ -34,10 +34,9 @@ macro_rules! impl_consensus_encoding {
fn consensus_decode<R: $crate::io::Read + ?Sized>(
r: &mut R,
) -> Result<$thing, $crate::consensus::encode::Error> {
use crate::io::Read as _;
let mut r = r.take($crate::consensus::encode::MAX_VEC_SIZE as u64);
Ok($thing {
$($field: $crate::consensus::Decodable::consensus_decode(r.by_ref())?),+
$($field: $crate::consensus::Decodable::consensus_decode(&mut r)?),+
})
}
}

View File

@ -23,7 +23,7 @@
//! deserialization.
//! * `secp-lowmemory` - optimizations for low-memory devices.
//! * `no-std` - enables additional features required for this crate to be usable
//! without std. Does **not** disable `std`. Depends on `core2`.
//! without std. Does **not** disable `std`.
//! * `bitcoinconsensus-std` - enables `std` in `bitcoinconsensus` and communicates it
//! to this crate so it knows how to implement
//! `std::error::Error`. At this time there's a hack to
@ -68,9 +68,6 @@ pub extern crate bech32;
/// Bitcoin's libbitcoinconsensus with Rust binding.
pub extern crate bitcoinconsensus;
#[cfg(not(feature = "std"))]
extern crate core2;
/// Rust implementation of cryptographic hash function algorithems.
pub extern crate hashes;
@ -116,18 +113,7 @@ pub mod sign_message;
pub mod string;
pub mod taproot;
// May depend on crate features and we don't want to bother with it
#[allow(unused)]
#[cfg(feature = "std")]
use std::error::Error as StdError;
#[cfg(feature = "std")]
use std::io;
#[allow(unused)]
#[cfg(not(feature = "std"))]
use core2::error::Error as StdError;
#[cfg(not(feature = "std"))]
use core2::io;
use bitcoin_io::io;
#[rustfmt::skip] // Keep public re-exports separate.
#[doc(inline)]
@ -161,6 +147,8 @@ pub use crate::{
#[cfg(not(feature = "std"))]
mod io_extras {
use crate::io;
/// A writer which will move data into the void.
pub struct Sink {
_priv: (),
@ -169,12 +157,12 @@ mod io_extras {
/// Creates an instance of a writer which will successfully consume all data.
pub const fn sink() -> Sink { Sink { _priv: () } }
impl core2::io::Write for Sink {
impl io::Write for Sink {
#[inline]
fn write(&mut self, buf: &[u8]) -> core2::io::Result<usize> { Ok(buf.len()) }
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { Ok(buf.len()) }
#[inline]
fn flush(&mut self) -> core2::io::Result<()> { Ok(()) }
fn flush(&mut self) -> io::Result<()> { Ok(()) }
}
}
@ -196,34 +184,10 @@ mod prelude {
pub use std::collections::{BTreeMap, BTreeSet, btree_map, BinaryHeap};
#[cfg(feature = "std")]
pub use std::io::sink;
pub use crate::io::sink;
#[cfg(not(feature = "std"))]
pub use crate::io_extras::sink;
pub use hex::DisplayHex;
}
#[cfg(bench)]
use bench::EmptyWrite;
#[cfg(bench)]
mod bench {
use core::fmt::Arguments;
use crate::io::{IoSlice, Result, Write};
#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct EmptyWrite;
impl Write for EmptyWrite {
fn write(&mut self, buf: &[u8]) -> Result<usize> { Ok(buf.len()) }
fn write_vectored(&mut self, bufs: &[IoSlice]) -> Result<usize> {
Ok(bufs.iter().map(|s| s.len()).sum())
}
fn flush(&mut self) -> Result<()> { Ok(()) }
fn write_all(&mut self, _: &[u8]) -> Result<()> { Ok(()) }
fn write_fmt(&mut self, _: Arguments) -> Result<()> { Ok(()) }
}
}

View File

@ -116,7 +116,7 @@ impl fmt::Debug for Address {
impl ToSocketAddrs for Address {
type Iter = iter::Once<SocketAddr>;
fn to_socket_addrs(&self) -> Result<Self::Iter, io::Error> {
fn to_socket_addrs(&self) -> Result<Self::Iter, std::io::Error> {
Ok(iter::once(self.socket_addr()?))
}
}
@ -296,7 +296,7 @@ impl Decodable for AddrV2Message {
impl ToSocketAddrs for AddrV2Message {
type Iter = iter::Once<SocketAddr>;
fn to_socket_addrs(&self) -> Result<Self::Iter, io::Error> {
fn to_socket_addrs(&self) -> Result<Self::Iter, std::io::Error> {
Ok(iter::once(self.socket_addr()?))
}
}

View File

@ -10,7 +10,6 @@ use core::convert::TryFrom;
use core::{fmt, iter};
use hashes::{sha256d, Hash};
use io::Read as _;
use crate::blockdata::{block, transaction};
use crate::consensus::encode::{self, CheckedData, Decodable, Encodable, VarInt};
@ -429,7 +428,7 @@ impl Decodable for HeaderDeserializationWrapper {
#[inline]
fn consensus_decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
Self::consensus_decode_from_finite_reader(r.take(MAX_MSG_SIZE as u64).by_ref())
Self::consensus_decode_from_finite_reader(&mut r.take(MAX_MSG_SIZE as u64))
}
}
@ -534,7 +533,7 @@ impl Decodable for RawNetworkMessage {
#[inline]
fn consensus_decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
Self::consensus_decode_from_finite_reader(r.take(MAX_MSG_SIZE as u64).by_ref())
Self::consensus_decode_from_finite_reader(&mut r.take(MAX_MSG_SIZE as u64))
}
}

View File

@ -5,10 +5,9 @@
//! This module describes BIP37 Connection Bloom filtering network messages.
//!
use std::io;
use crate::consensus::{encode, Decodable, Encodable, ReadExt};
use crate::internal_macros::impl_consensus_encoding;
use crate::io;
/// `filterload` message sets the current bloom filter
#[derive(Clone, PartialEq, Eq, Debug)]

View File

@ -196,7 +196,7 @@ where
}
// core2 doesn't have read_to_end
pub(crate) fn read_to_end<D: io::Read>(mut d: D) -> Result<Vec<u8>, io::Error> {
pub(crate) fn read_to_end<D: io::Read + ?Sized>(d: &mut D) -> Result<Vec<u8>, io::Error> {
let mut result = vec![];
let mut buf = [0u8; 64];
loop {

View File

@ -1112,7 +1112,7 @@ impl TaprootMerkleBranch {
/// # Returns
///
/// The number of bytes written to the writer.
pub fn encode<Write: io::Write>(&self, mut writer: Write) -> io::Result<usize> {
pub fn encode<Write: io::Write + ?Sized>(&self, writer: &mut Write) -> io::Result<usize> {
for hash in self.0.iter() {
writer.write_all(hash.as_ref())?;
}
@ -1240,12 +1240,12 @@ impl ControlBlock {
/// # Returns
///
/// The number of bytes written to the writer.
pub fn encode<Write: io::Write>(&self, mut writer: Write) -> io::Result<usize> {
pub fn encode<Write: io::Write + ?Sized>(&self, writer: &mut Write) -> io::Result<usize> {
let first_byte: u8 =
i32::from(self.output_key_parity) as u8 | self.leaf_version.to_consensus();
writer.write_all(&[first_byte])?;
writer.write_all(&self.internal_key.serialize())?;
self.merkle_branch.encode(&mut writer)?;
self.merkle_branch.encode(writer)?;
Ok(self.size())
}

View File

@ -17,8 +17,6 @@ if cargo --version | grep ${MSRV}; then
cargo update -p schemars --precise 0.8.12
# schemars_derive 0.8.13 uses edition 2021
cargo update -p schemars_derive --precise 0.8.12
# memchr 2.6.0 uses edition 2021
cargo update -p memchr --precise 2.5.0
# byteorder 1.5.0 uses edition 2021
cargo update -p byteorder --precise 1.4.3

View File

@ -14,11 +14,11 @@ exclude = ["tests", "contrib"]
[features]
default = ["std"]
std = ["alloc", "hex/std"]
std = ["alloc", "hex/std", "bitcoin-io/std"]
alloc = ["hex/alloc"]
serde-std = ["serde/std"]
# If you want I/O you must enable either "std" or "core2".
core2 = ["actual-core2", "hex/core2"]
# If you want I/O you must enable either "std" or "io".
io = ["bitcoin-io"]
# Smaller (but slower) implementation of sha256, sha512 and ripemd160
small-hash = []
@ -29,13 +29,12 @@ rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
hex = { package = "hex-conservative", version = "0.1.1", default-features = false }
bitcoin-io = { version = "0.1", default-features = false, optional = true }
schemars = { version = "0.8.3", optional = true }
# Only enable this if you explicitly do not want to use "std", otherwise enable "serde-std".
serde = { version = "1.0", default-features = false, optional = true }
# Do NOT use this feature! Use the "core2" feature instead.
actual-core2 = { package = "core2", version = "0.3.2", default-features = false, optional = true }
[dev-dependencies]
serde_test = "1.0"
serde_json = "1.0"

View File

@ -2,7 +2,7 @@
set -ex
FEATURES="serde serde-std std core2 alloc"
FEATURES="serde serde-std std io alloc"
cargo --version
rustc --version

View File

@ -18,8 +18,8 @@ cortex-m-rt = "0.6.10"
cortex-m-semihosting = "0.3.3"
panic-halt = "0.2.0"
alloc-cortex-m = { version = "0.4.1", optional = true }
bitcoin_hashes = { path="../", default-features = false, features = ["core2"] }
core2 = { version = "0.3.0", default_features = false }
bitcoin_hashes = { path="../", default-features = false, features = ["io"] }
bitcoin-io = { path = "../../io", default_features = false }
[[bin]]
name = "embedded"
@ -33,3 +33,6 @@ lto = true # better optimizations
[patch.crates-io.bitcoin-internals]
path = "../../internals"
[patch.crates-io.bitcoin-io]
path = "../../io"

View File

@ -10,7 +10,7 @@ extern crate bitcoin_hashes;
#[cfg(feature = "alloc")] use alloc::string::ToString;
use bitcoin_hashes::{sha256, Hash, HashEngine};
use core2::io::Write;
use bitcoin_io::io::Write;
use core::str::FromStr;
use cortex_m_rt::entry;
use cortex_m_semihosting::{debug, hprintln};

View File

@ -21,3 +21,6 @@ serde_json = "1.0"
[patch.crates-io.bitcoin-internals]
path = "../../../internals"
[patch.crates-io.bitcoin-io]
path = "../../../io"

View File

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

View File

@ -81,8 +81,6 @@
// Exclude clippy lints we don't think are valuable
#![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134
#[cfg(all(not(test), not(feature = "std"), feature = "core2"))]
extern crate actual_core2 as core2;
#[cfg(all(feature = "alloc", not(feature = "std")))]
extern crate alloc;
#[cfg(any(test, feature = "std"))]
@ -119,7 +117,7 @@ pub mod serde_macros;
pub mod cmp;
pub mod hash160;
pub mod hmac;
#[cfg(any(test, feature = "std", feature = "core2"))]
#[cfg(feature = "bitcoin-io")]
mod impls;
pub mod ripemd160;
pub mod sha1;
@ -131,12 +129,7 @@ pub mod sha512_256;
pub mod siphash24;
use core::{borrow, fmt, hash, ops};
// You get I/O if you enable "std" or "core2" (as well as during testing).
#[cfg(any(test, feature = "std"))]
use std::io;
#[cfg(all(not(test), not(feature = "std"), feature = "core2"))]
use core2::io;
pub use hmac::{Hmac, HmacEngine};
/// A hashing engine which bytes can be serialized into.

22
io/Cargo.toml Normal file
View File

@ -0,0 +1,22 @@
[package]
name = "bitcoin-io"
version = "0.1.0"
authors = ["Matt Corallo <birchneutea@mattcorallo.com>"]
license = "CC0-1.0"
repository = "https://github.com/rust-bitcoin/rust-bitcoin"
documentation = "https://docs.rs/bitcoin-io/"
description = "Simple I/O traits for no-std (and std) environments"
categories = ["no-std"]
keywords = [ "io", "no-std" ]
readme = "README.md"
edition = "2018"
exclude = ["tests", "contrib"]
[features]
default = ["std"]
std = []
alloc = []
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

425
io/src/lib.rs Normal file
View File

@ -0,0 +1,425 @@
//! Rust-Bitcoin IO Library
//!
//! Because the core `std::io` module is not yet exposed in `no-std` Rust, building `no-std`
//! applications which require reading and writing objects via standard traits is not generally
//! possible. While there is ongoing work to improve this situation, this module is not likely to
//! be available for applications with broad rustc version support for some time.
//!
//! Thus, this library exists to export a minmal version of `std::io`'s traits which `no-std`
//! applications may need. With the `std` feature, these traits are also implemented for the
//! `std::io` traits, allowing standard objects to be used wherever the traits from this crate are
//! required.
//!
//! This traits are not one-for-one drop-ins, but are as close as possible while still implementing
//! `std::io`'s traits without unnecessary complexity.
// Experimental features we need.
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(any(feature = "alloc", feature = "std"))]
extern crate alloc;
/// Standard I/O stream definitions which are API-equivalent to `std`'s `io` module. See
/// [`std::io`] for more info.
pub mod io {
use core::convert::TryInto;
use core::fmt::{Debug, Display, Formatter};
#[cfg(any(feature = "alloc", feature = "std"))]
use alloc::boxed::Box;
#[cfg(all(feature = "alloc", not(feature = "std")))]
mod sealed {
use alloc::boxed::Box;
use alloc::string::String;
use core::fmt::Debug;
pub trait IntoBoxDynDebug {
fn into(self) -> Box<dyn Debug + Send + Sync + 'static>;
}
impl IntoBoxDynDebug for &str {
fn into(self) -> Box<dyn Debug + Send + Sync + 'static> {
Box::new(String::from(self))
}
}
impl IntoBoxDynDebug for String {
fn into(self) -> Box<dyn Debug + Send + Sync + 'static> {
Box::new(self)
}
}
}
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
#[cfg(feature = "std")]
error: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
#[cfg(all(feature = "alloc", not(feature = "std")))]
error: Option<Box<dyn Debug + Send + Sync + 'static>>,
}
impl Error {
#[cfg(feature = "std")]
pub fn new<E>(kind: ErrorKind, error: E) -> Error
where
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
{
Self { kind, error: Some(error.into()) }
}
#[cfg(all(feature = "alloc", not(feature = "std")))]
pub fn new<E: sealed::IntoBoxDynDebug>(kind: ErrorKind, error: E) -> Error {
Self { kind, error: Some(error.into()) }
}
pub fn kind(&self) -> ErrorKind {
self.kind
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Error {
Self {
kind,
#[cfg(any(feature = "std", feature = "alloc"))]
error: None,
}
}
}
impl Display for Error {
fn fmt(&self, fmt: &mut Formatter) -> core::result::Result<(), core::fmt::Error> {
fmt.write_fmt(format_args!("I/O Error: {}", self.kind.description()))?;
#[cfg(any(feature = "alloc", feature = "std"))]
if let Some(e) = &self.error {
fmt.write_fmt(format_args!(". {:?}", e))?;
}
Ok(())
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.error.as_ref().and_then(|e| e.as_ref().source())
}
#[allow(deprecated)]
fn description(&self) -> &str {
match self.error.as_ref() {
Some(e) => e.description(),
None => self.kind.description(),
}
}
#[allow(deprecated)]
fn cause(&self) -> Option<&dyn std::error::Error> {
self.error.as_ref().and_then(|e| e.as_ref().cause())
}
}
impl Error {
#[cfg(feature = "std")]
pub fn get_ref(&self) -> Option<&(dyn std::error::Error + Send + Sync + 'static)> {
self.error.as_deref()
}
#[cfg(all(feature = "alloc", not(feature = "std")))]
pub fn get_ref(&self) -> Option<&(dyn Debug + Send + Sync + 'static)> {
self.error.as_deref()
}
}
#[cfg(feature = "std")]
impl From<std::io::Error> for Error {
fn from(o: std::io::Error) -> Error {
Self { kind: ErrorKind::from_std(o.kind()), error: o.into_inner() }
}
}
#[cfg(feature = "std")]
impl From<Error> for std::io::Error {
fn from(o: Error) -> std::io::Error {
if let Some(err) = o.error {
std::io::Error::new(o.kind.to_std(), err)
} else {
o.kind.to_std().into()
}
}
}
macro_rules! define_errorkind {
($($kind: ident),*) => {
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
/// A minimal subset of [`std::io::ErrorKind`] which is used for [`Error`]. Note that, as with
/// [`std::io`], only [`Self::Interrupted`] has defined semantics in this crate, all other
/// variants are provided here only to provide higher-fidelity conversions to and from
/// [`std::io::Error`].
pub enum ErrorKind {
$($kind),*
}
impl ErrorKind {
fn description(&self) -> &'static str {
match self {
$(Self::$kind => stringify!($kind)),*
}
}
#[cfg(feature = "std")]
fn to_std(self) -> std::io::ErrorKind {
match self {
$(Self::$kind => std::io::ErrorKind::$kind),*
}
}
#[cfg(feature = "std")]
fn from_std(o: std::io::ErrorKind) -> ErrorKind {
match o {
$(std::io::ErrorKind::$kind => ErrorKind::$kind),*,
_ => ErrorKind::Other
}
}
}
}
}
define_errorkind!(
NotFound,
PermissionDenied,
ConnectionRefused,
ConnectionReset,
ConnectionAborted,
NotConnected,
AddrInUse,
AddrNotAvailable,
BrokenPipe,
AlreadyExists,
WouldBlock,
InvalidInput,
InvalidData,
TimedOut,
WriteZero,
Interrupted,
UnexpectedEof,
// Note: Any time we bump the MSRV any new error kinds should be added here!
Other
);
pub type Result<T> = core::result::Result<T, Error>;
/// A generic trait describing an input stream. See [`std::io::Read`] for more info.
pub trait Read {
fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
#[inline]
fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<()> {
while !buf.is_empty() {
match self.read(buf) {
Ok(0) => return Err(ErrorKind::UnexpectedEof.into()),
Ok(len) => buf = &mut buf[len..],
Err(e) if e.kind() == ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
Ok(())
}
#[inline]
fn take(&mut self, limit: u64) -> Take<Self> {
Take { reader: self, remaining: limit }
}
}
pub struct Take<'a, R: Read + ?Sized> {
reader: &'a mut R,
remaining: u64,
}
impl<'a, R: Read + ?Sized> Read for Take<'a, R> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
let len = core::cmp::min(buf.len(), self.remaining.try_into().unwrap_or(buf.len()));
let read = self.reader.read(&mut buf[..len])?;
self.remaining -= read.try_into().unwrap_or(self.remaining);
Ok(read)
}
}
#[cfg(feature = "std")]
impl<R: std::io::Read> Read for R {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
Ok(<R as std::io::Read>::read(self, buf)?)
}
}
#[cfg(not(feature = "std"))]
impl Read for &[u8] {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
let cnt = core::cmp::min(self.len(), buf.len());
buf[..cnt].copy_from_slice(&self[..cnt]);
*self = &self[cnt..];
Ok(cnt)
}
}
pub struct Cursor<T> {
inner: T,
pos: u64,
}
impl<T: AsRef<[u8]>> Cursor<T> {
#[inline]
pub fn new(inner: T) -> Self {
Cursor { inner, pos: 0 }
}
#[inline]
pub fn position(&self) -> u64 {
self.pos
}
#[inline]
pub fn into_inner(self) -> T {
self.inner
}
}
impl<T: AsRef<[u8]>> Read for Cursor<T> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
let inner: &[u8] = self.inner.as_ref();
let start_pos = self.pos.try_into().unwrap_or(inner.len());
let read = core::cmp::min(inner.len().saturating_sub(start_pos), buf.len());
buf[..read].copy_from_slice(&inner[start_pos..start_pos + read]);
self.pos = self.pos.saturating_add(read.try_into().unwrap_or(u64::max_value() /* unreachable */));
Ok(read)
}
}
/// A generic trait describing an output stream. See [`std::io::Write`] for more info.
pub trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize>;
fn flush(&mut self) -> Result<()>;
#[inline]
fn write_all(&mut self, mut buf: &[u8]) -> Result<()> {
while !buf.is_empty() {
match self.write(buf) {
Ok(0) => return Err(ErrorKind::UnexpectedEof.into()),
Ok(len) => buf = &buf[len..],
Err(e) if e.kind() == ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
Ok(())
}
}
#[cfg(feature = "std")]
impl<W: std::io::Write> Write for W {
#[inline]
fn write(&mut self, buf: &[u8]) -> Result<usize> {
Ok(<W as std::io::Write>::write(self, buf)?)
}
#[inline]
fn flush(&mut self) -> Result<()> {
Ok(<W as std::io::Write>::flush(self)?)
}
}
#[cfg(all(feature = "alloc", not(feature = "std")))]
impl Write for alloc::vec::Vec<u8> {
#[inline]
fn write(&mut self, buf: &[u8]) -> Result<usize> {
self.extend_from_slice(buf);
Ok(buf.len())
}
#[inline]
fn flush(&mut self) -> Result<()> { Ok(()) }
}
#[cfg(not(feature = "std"))]
impl<'a> Write for &'a mut [u8] {
#[inline]
fn write(&mut self, buf: &[u8]) -> Result<usize> {
let cnt = core::cmp::min(self.len(), buf.len());
self[..cnt].copy_from_slice(&buf[..cnt]);
*self = &mut core::mem::take(self)[cnt..];
Ok(cnt)
}
#[inline]
fn flush(&mut self) -> Result<()> { Ok(()) }
}
/// A sink to which all writes succeed. See [`std::io::Sink`] for more info.
pub struct Sink;
#[cfg(not(feature = "std"))]
impl Write for Sink {
#[inline]
fn write(&mut self, buf: &[u8]) -> Result<usize> {
Ok(buf.len())
}
#[inline]
fn write_all(&mut self, _: &[u8]) -> Result<()> { Ok(()) }
#[inline]
fn flush(&mut self) -> Result<()> { Ok(()) }
}
#[cfg(feature = "std")]
impl std::io::Write for Sink {
#[inline]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
Ok(buf.len())
}
#[inline]
fn write_all(&mut self, _: &[u8]) -> std::io::Result<()> { Ok(()) }
#[inline]
fn flush(&mut self) -> std::io::Result<()> { Ok(()) }
}
/// Returns a sink to which all writes succeed. See [`std::io::sink`] for more info.
pub fn sink() -> Sink { Sink }
}
#[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)
}
}
}
}