Skip to content

Commit ee701e4

Browse files
committed
Merge rust-bitcoin#4982: Implement SliceEncoder
a775d68 consenus_encoding: Add a SliceEncoder (Tobin C. Harding) 252eb0a Add encoder wrapper type integration test (Tobin C. Harding) Pull request description: Add some integration test for how we expect the `consensus_encoding` crate to be used (i.e., with wrapper encoder types). Then implement a `SliceEncoder` - BOOM! ACKs for top commit: apoelstra: ACK a775d68; successfully ran local tests nyonson: ACK a775d68 Tree-SHA512: 6ab73b685925aca7c49688d3604af0965e70c653f06c4a99e12bd546b64c903b2bb00e90ffd5b7d21b92d13279e106509a0cfe2573fac799cb870b7d0aad530c
2 parents e8c2601 + a775d68 commit ee701e4

File tree

6 files changed

+306
-4
lines changed

6 files changed

+306
-4
lines changed

consensus_encoding/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ internals = { package = "bitcoin-internals", path = "../internals" }
2525
all-features = true
2626
rustdoc-args = ["--cfg", "docsrs"]
2727

28+
[[example]]
29+
name = "encoder"
30+
required-features = ["alloc"]
31+
2832
[lints.rust]
2933
unexpected_cfgs = { level = "deny", check-cfg = [] }
3034

consensus_encoding/contrib/test_vars.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ FEATURES_WITH_STD=""
1111
FEATURES_WITHOUT_STD="alloc"
1212

1313
# Run these examples.
14-
EXAMPLES=""
14+
EXAMPLES="encoder:alloc"
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// SPDX-License-Identifier: CC0-1.0
2+
3+
//! Example of creating an encoder that encodes a slice of encodable objects.
4+
5+
use consensus_encoding as encoding;
6+
use encoding::{ArrayEncoder, BytesEncoder, Encodable, Encoder2, SliceEncoder};
7+
8+
fn main() {
9+
let v = vec![Inner::new(0xcafe_babe), Inner::new(0xdead_beef)];
10+
let b = vec![0xab, 0xcd];
11+
12+
let adt = Adt::new(v, b);
13+
let encoded = encoding::encode_to_vec(&adt);
14+
15+
let want = [0x02, 0xca, 0xfe, 0xba, 0xbe, 0xde, 0xad, 0xbe, 0xef, 0xab, 0xcd];
16+
assert_eq!(encoded, want);
17+
}
18+
19+
/// Some abstract data type.
20+
struct Adt {
21+
v: Vec<Inner>,
22+
b: Vec<u8>,
23+
}
24+
25+
impl Adt {
26+
/// Constructs a new `Adt`.
27+
pub fn new(v: Vec<Inner>, b: Vec<u8>) -> Self { Self { v, b } }
28+
}
29+
30+
encoding::encoder_newtype! {
31+
/// The encoder for the [`Adt`] type.
32+
pub struct AdtEncoder<'e>(Encoder2<SliceEncoder<'e, Inner>, BytesEncoder<'e>>);
33+
}
34+
35+
impl Encodable for Adt {
36+
type Encoder<'a>
37+
= AdtEncoder<'a>
38+
where
39+
Self: 'a;
40+
41+
fn encoder(&self) -> Self::Encoder<'_> {
42+
let a = SliceEncoder::with_length_prefix(&self.v);
43+
let b = BytesEncoder::without_length_prefix(self.b.as_ref());
44+
45+
AdtEncoder(Encoder2::new(a, b))
46+
}
47+
}
48+
49+
/// A simple data type to use as list item.
50+
#[derive(Debug, Default, Clone)]
51+
pub struct Inner(u32);
52+
53+
impl Inner {
54+
/// Constructs a new `Inner`.
55+
pub fn new(x: u32) -> Self { Self(x) }
56+
57+
/// Returns some meaningful 4 byte array for this type.
58+
pub fn to_array(&self) -> [u8; 4] { self.0.to_be_bytes() }
59+
}
60+
61+
encoding::encoder_newtype! {
62+
/// The encoder for the [`Inner`] type.
63+
pub struct InnerEncoder(ArrayEncoder<4>);
64+
}
65+
66+
impl Encodable for Inner {
67+
type Encoder<'e> = InnerEncoder;
68+
fn encoder(&self) -> Self::Encoder<'_> {
69+
InnerEncoder(ArrayEncoder::without_length_prefix(self.to_array()))
70+
}
71+
}

consensus_encoding/src/encode/encoders.rs

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
use internals::array_vec::ArrayVec;
1515
use internals::compact_size;
1616

17-
use super::Encoder;
17+
use super::{Encodable, Encoder};
1818

1919
/// The maximum length of a compact size encoding.
2020
const SIZE: usize = compact_size::MAX_ENCODING_SIZE;
@@ -76,6 +76,78 @@ impl<const N: usize> Encoder<'_> for ArrayEncoder<N> {
7676
}
7777
}
7878

79+
/// An encoder for a list of encodable types.
80+
pub struct SliceEncoder<'e, T: Encodable> {
81+
/// The list of references to the objects we are encoding.
82+
///
83+
/// This is **never** mutated. All accesses are done by array accesses because
84+
/// of lifetimes and the borrow checker.
85+
sl: &'e [T],
86+
/// The length prefix.
87+
compact_size: Option<ArrayVec<u8, SIZE>>,
88+
/// Index into `sl` of the element we are currently encoding.
89+
cur_idx: usize,
90+
/// Current encoder (for `sl[self.cur_idx]`).
91+
cur_enc: Option<T::Encoder<'e>>,
92+
}
93+
94+
impl<'e, T: Encodable> SliceEncoder<'e, T> {
95+
/// Constructs an encoder which encodes the slice with a length prefix.
96+
pub fn with_length_prefix(sl: &'e [T]) -> Self {
97+
let len = sl.len();
98+
let compact_size = Some(compact_size::encode(len));
99+
100+
// In this `map` call we cannot remove the closure. Seems to be a bug in the compiler.
101+
// Perhaps https://github.com/rust-lang/rust/issues/102540 which is 3 years old with
102+
// no replies or even an acknowledgement. We will not bother filing our own issue.
103+
Self { sl, compact_size, cur_idx: 0, cur_enc: sl.first().map(|x| T::encoder(x)) }
104+
}
105+
}
106+
107+
impl<'e, T: Encodable> Encoder<'e> for SliceEncoder<'e, T> {
108+
fn current_chunk(&self) -> Option<&[u8]> {
109+
if let Some(compact_size) = self.compact_size.as_ref() {
110+
return Some(compact_size);
111+
}
112+
113+
// `advance` sets `cur_enc` to `None` once the slice encoder is completely exhausted.
114+
// `current_chunk` is required to return `None` if called after the encoder is exhausted.
115+
self.cur_enc.as_ref().and_then(T::Encoder::current_chunk)
116+
}
117+
118+
fn advance(&mut self) -> bool {
119+
let Some(cur) = self.cur_enc.as_mut() else {
120+
return false;
121+
};
122+
123+
loop {
124+
if self.compact_size.is_some() {
125+
// On the first call to advance(), just mark the compact_size as already
126+
// yielded and leave self.cur_idx at 0.
127+
self.compact_size = None;
128+
} else {
129+
// On subsequent calls, attempt to advance the current encoder and return
130+
// success if this succeeds.
131+
if cur.advance() {
132+
return true;
133+
}
134+
self.cur_idx += 1;
135+
}
136+
137+
// If advancing the current encoder failed, attempt to move to the next encoder.
138+
if let Some(x) = self.sl.get(self.cur_idx) {
139+
*cur = x.encoder();
140+
if cur.current_chunk().is_some() {
141+
return true;
142+
}
143+
} else {
144+
self.cur_enc = None; // shortcut the next call to advance()
145+
return false;
146+
}
147+
}
148+
}
149+
}
150+
79151
/// An encoder which encodes two objects, one after the other.
80152
pub struct Encoder2<A, B> {
81153
enc_idx: usize,
@@ -239,4 +311,4 @@ mod tests {
239311
let want = [0u8];
240312
assert_eq!(got, want);
241313
}
242-
}
314+
}

consensus_encoding/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,6 @@ pub use self::encode::encode_to_vec;
3131
#[cfg(feature = "std")]
3232
pub use self::encode::encode_to_writer;
3333
pub use self::encode::encoders::{
34-
ArrayEncoder, BytesEncoder, Encoder2, Encoder3, Encoder4, Encoder6,
34+
ArrayEncoder, BytesEncoder, Encoder2, Encoder3, Encoder4, Encoder6, SliceEncoder,
3535
};
3636
pub use self::encode::{encode_to_hash_engine, Encodable, Encoder};
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
//! Test using wrapper types as we expect the lib to be used.
2+
3+
#![cfg(feature = "std")]
4+
5+
use consensus_encoding as encoding;
6+
use encoding::{ArrayEncoder, BytesEncoder, Encodable, Encoder2, SliceEncoder};
7+
8+
encoding::encoder_newtype! {
9+
/// An encoder that uses an inner `ArrayEncoder`.
10+
pub struct TestArrayEncoder(ArrayEncoder<4>);
11+
}
12+
13+
encoding::encoder_newtype! {
14+
/// An encoder that uses an inner `BytesEncoder`.
15+
pub struct TestBytesEncoder<'e>(BytesEncoder<'e>);
16+
}
17+
18+
#[test]
19+
fn array_encoder() {
20+
#[derive(Debug, Default, Clone)]
21+
pub struct Test(u32);
22+
23+
impl Encodable for Test {
24+
type Encoder<'e> = TestArrayEncoder;
25+
fn encoder(&self) -> Self::Encoder<'_> {
26+
TestArrayEncoder(ArrayEncoder::without_length_prefix(self.0.to_le_bytes()))
27+
}
28+
}
29+
30+
let t = Test(0xcafe_babe); // Encodes using an array.
31+
32+
let want = [0xbe, 0xba, 0xfe, 0xca];
33+
let got = encoding::encode_to_vec(&t);
34+
35+
assert_eq!(got, want);
36+
}
37+
38+
#[test]
39+
fn bytes_encoder_without_length_prefix() {
40+
#[derive(Debug, Default, Clone)]
41+
pub struct Test(Vec<u8>);
42+
43+
impl Encodable for Test {
44+
type Encoder<'e>
45+
= TestBytesEncoder<'e>
46+
where
47+
Self: 'e;
48+
49+
fn encoder(&self) -> Self::Encoder<'_> {
50+
TestBytesEncoder(BytesEncoder::without_length_prefix(self.0.as_ref()))
51+
}
52+
}
53+
54+
let t = Test(vec![0xca, 0xfe]);
55+
56+
let want = [0xca, 0xfe];
57+
let got = encoding::encode_to_vec(&t);
58+
59+
assert_eq!(got, want);
60+
}
61+
62+
#[test]
63+
fn bytes_encoder_with_length_prefix() {
64+
#[derive(Debug, Default, Clone)]
65+
pub struct Test(Vec<u8>);
66+
67+
impl Encodable for Test {
68+
type Encoder<'e>
69+
= TestBytesEncoder<'e>
70+
where
71+
Self: 'e;
72+
73+
fn encoder(&self) -> Self::Encoder<'_> {
74+
TestBytesEncoder(BytesEncoder::with_length_prefix(self.0.as_ref()))
75+
}
76+
}
77+
78+
let t = Test(vec![0xca, 0xfe]);
79+
80+
let want = [0x02, 0xca, 0xfe];
81+
let got = encoding::encode_to_vec(&t);
82+
83+
assert_eq!(got, want);
84+
}
85+
86+
#[test]
87+
fn two_encoder() {
88+
#[derive(Debug, Default, Clone)]
89+
pub struct Test {
90+
a: Vec<u8>, // Encode without prefix.
91+
b: Vec<u8>, // Encode with prefix.
92+
}
93+
94+
impl Encodable for Test {
95+
type Encoder<'e> = Encoder2<TestBytesEncoder<'e>, TestBytesEncoder<'e>>;
96+
97+
fn encoder(&self) -> Self::Encoder<'_> {
98+
let a = TestBytesEncoder(BytesEncoder::without_length_prefix(self.a.as_ref()));
99+
let b = TestBytesEncoder(BytesEncoder::with_length_prefix(self.b.as_ref()));
100+
101+
Encoder2::new(a, b)
102+
}
103+
}
104+
105+
let t = Test { a: vec![0xca, 0xfe], b: (vec![0xba, 0xbe]) };
106+
107+
let want = [0xca, 0xfe, 0x02, 0xba, 0xbe];
108+
let got = encoding::encode_to_vec(&t);
109+
110+
assert_eq!(got, want);
111+
}
112+
113+
#[test]
114+
fn slice_encoder() {
115+
#[derive(Debug, Default, Clone)]
116+
pub struct Test(Vec<Inner>);
117+
118+
encoding::encoder_newtype! {
119+
/// An encoder that uses an inner `SliceEncoder`.
120+
pub struct TestEncoder<'e>(SliceEncoder<'e, Inner>);
121+
}
122+
123+
impl Encodable for Test {
124+
type Encoder<'a>
125+
= TestEncoder<'a>
126+
where
127+
Self: 'a;
128+
129+
fn encoder(&self) -> Self::Encoder<'_> {
130+
TestEncoder(SliceEncoder::with_length_prefix(&self.0))
131+
}
132+
}
133+
134+
#[derive(Debug, Default, Clone)]
135+
pub struct Inner(u32);
136+
137+
encoding::encoder_newtype! {
138+
/// The encoder for the [`Inner`] type.
139+
pub struct InnerArrayEncoder(ArrayEncoder<4>);
140+
}
141+
142+
impl Encodable for Inner {
143+
type Encoder<'e> = InnerArrayEncoder;
144+
fn encoder(&self) -> Self::Encoder<'_> {
145+
// Big-endian to make reading the test assertion easier.
146+
InnerArrayEncoder(ArrayEncoder::without_length_prefix(self.0.to_be_bytes()))
147+
}
148+
}
149+
150+
let t = Test(vec![Inner(0xcafe_babe), Inner(0xdead_beef)]);
151+
let encoded = encoding::encode_to_vec(&t);
152+
153+
let want = [0x02, 0xca, 0xfe, 0xba, 0xbe, 0xde, 0xad, 0xbe, 0xef];
154+
assert_eq!(encoded, want);
155+
}

0 commit comments

Comments
 (0)