Skip to content

Commit a775d68

Browse files
committed
consenus_encoding: Add a SliceEncoder
Add an encoder that can be used to encode a slice of `T: Encodable` types. Will be used by `Transaction`. Add an example usage as well as an integration test. The `Encoder` impl was written during review by Andrew, props. Co-developed-by: Andrew Poelstra <apoelstra@wpsoftware.net>
1 parent 252eb0a commit a775d68

File tree

6 files changed

+196
-5
lines changed

6 files changed

+196
-5
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};

consensus_encoding/tests/wrappers.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#![cfg(feature = "std")]
44

55
use consensus_encoding as encoding;
6-
use encoding::{ArrayEncoder, BytesEncoder, Encodable, Encoder2};
6+
use encoding::{ArrayEncoder, BytesEncoder, Encodable, Encoder2, SliceEncoder};
77

88
encoding::encoder_newtype! {
99
/// An encoder that uses an inner `ArrayEncoder`.
@@ -109,3 +109,47 @@ fn two_encoder() {
109109

110110
assert_eq!(got, want);
111111
}
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)