Skip to content

Commit d30595e

Browse files
committed
consensus_encoding: add standard I/O drivers
Allows encoders to easily dump bytes into a vector or writer depending on if the alloc or std features are enabled.
1 parent 09477ce commit d30595e

File tree

3 files changed

+145
-0
lines changed

3 files changed

+145
-0
lines changed

consensus_encoding/src/encode/mod.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
//! Consensus Encoding Traits
44
5+
#[cfg(feature = "alloc")]
6+
use alloc::vec::Vec;
7+
58
pub mod encoders;
69

710
/// A Bitcoin object which can be consensus-encoded.
@@ -74,3 +77,40 @@ pub fn encode_to_hash_engine<T: Encodable, H: hashes::HashEngine>(object: &T, mu
7477
}
7578
engine
7679
}
80+
81+
/// Encodes an object into a vector.
82+
#[cfg(feature = "alloc")]
83+
pub fn encode_to_vec<T: Encodable>(object: &T) -> Vec<u8> {
84+
let mut encoder = object.encoder();
85+
let mut vec = Vec::new();
86+
while let Some(chunk) = encoder.current_chunk() {
87+
vec.extend_from_slice(chunk);
88+
encoder.advance();
89+
}
90+
vec
91+
}
92+
93+
/// Encodes an object to a standard I/O writer.
94+
///
95+
/// # Performance
96+
///
97+
/// This method writes data in potentially small chunks based on the encoder's
98+
/// internal chunking strategy. For optimal performance with unbuffered writers
99+
/// (like [`std::fs::File`] or [`std::net::TcpStream`]), consider wrapping your
100+
/// writer with [`std::io::BufWriter`].
101+
///
102+
/// # Errors
103+
///
104+
/// Returns any I/O error encountered while writing to the writer.
105+
#[cfg(feature = "std")]
106+
pub fn encode_to_writer<T: Encodable, W: std::io::Write>(
107+
object: &T,
108+
mut writer: W,
109+
) -> Result<(), std::io::Error> {
110+
let mut encoder = object.encoder();
111+
while let Some(chunk) = encoder.current_chunk() {
112+
writer.write_all(chunk)?;
113+
encoder.advance();
114+
}
115+
Ok(())
116+
}

consensus_encoding/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,17 @@
1414
#![warn(deprecated_in_future)]
1515
#![doc(test(attr(warn(unused))))]
1616

17+
#[cfg(feature = "alloc")]
18+
extern crate alloc;
19+
#[cfg(feature = "std")]
20+
extern crate std;
21+
1722
mod encode;
1823

24+
#[cfg(feature = "alloc")]
25+
pub use self::encode::encode_to_vec;
26+
#[cfg(feature = "std")]
27+
pub use self::encode::encode_to_writer;
1928
pub use self::encode::encoders::{
2029
ArrayEncoder, BytesEncoder, Encoder2, Encoder3, Encoder4, Encoder6,
2130
};

consensus_encoding/tests/encode.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// SPDX-License-Identifier: CC0-1.0
2+
3+
//! Tests for encoder free functions.
4+
5+
#[cfg(feature = "std")]
6+
use std::io::{Cursor, Write};
7+
8+
use consensus_encoding::{ArrayEncoder, Encodable};
9+
10+
// Simple test type that implements Encodable.
11+
struct TestData(u32);
12+
13+
impl Encodable for TestData {
14+
type Encoder<'s>
15+
= ArrayEncoder<4>
16+
where
17+
Self: 's;
18+
19+
fn encoder(&self) -> Self::Encoder<'_> {
20+
ArrayEncoder::without_length_prefix(self.0.to_le_bytes())
21+
}
22+
}
23+
24+
// Test with a type that creates an empty encoder.
25+
struct EmptyData;
26+
27+
impl Encodable for EmptyData {
28+
type Encoder<'s>
29+
= ArrayEncoder<0>
30+
where
31+
Self: 's;
32+
33+
fn encoder(&self) -> Self::Encoder<'_> { ArrayEncoder::without_length_prefix([]) }
34+
}
35+
36+
#[test]
37+
#[cfg(feature = "std")]
38+
fn encode_std_writer() {
39+
let data = TestData(0x1234_5678);
40+
41+
let mut cursor = Cursor::new(Vec::new());
42+
consensus_encoding::encode_to_writer(&data, &mut cursor).unwrap();
43+
44+
let result = cursor.into_inner();
45+
assert_eq!(result, vec![0x78, 0x56, 0x34, 0x12]);
46+
}
47+
48+
#[test]
49+
#[cfg(feature = "alloc")]
50+
fn encode_vec() {
51+
let data = TestData(0xDEAD_BEEF);
52+
let vec = consensus_encoding::encode_to_vec(&data);
53+
assert_eq!(vec, vec![0xEF, 0xBE, 0xAD, 0xDE]);
54+
}
55+
56+
#[test]
57+
#[cfg(feature = "alloc")]
58+
fn encode_vec_empty_data() {
59+
let data = EmptyData;
60+
let result = consensus_encoding::encode_to_vec(&data);
61+
assert!(result.is_empty());
62+
}
63+
64+
#[test]
65+
#[cfg(feature = "std")]
66+
fn encode_std_writer_empty_data() {
67+
let data = EmptyData;
68+
let mut cursor = Cursor::new(Vec::new());
69+
consensus_encoding::encode_to_writer(&data, &mut cursor).unwrap();
70+
71+
let result = cursor.into_inner();
72+
assert!(result.is_empty());
73+
}
74+
75+
#[test]
76+
#[cfg(feature = "std")]
77+
fn encode_std_writer_io_error() {
78+
// Test writer that always fails.
79+
struct FailingWriter;
80+
81+
impl Write for FailingWriter {
82+
fn write(&mut self, _buf: &[u8]) -> std::io::Result<usize> {
83+
Err(std::io::Error::other("test error"))
84+
}
85+
86+
fn flush(&mut self) -> std::io::Result<()> { Ok(()) }
87+
}
88+
89+
let data = TestData(0x1234_5678);
90+
let mut writer = FailingWriter;
91+
92+
let result = consensus_encoding::encode_to_writer(&data, &mut writer);
93+
94+
assert!(result.is_err());
95+
assert_eq!(result.unwrap_err().kind(), std::io::ErrorKind::Other);
96+
}

0 commit comments

Comments
 (0)