Skip to content

Commit 9e79003

Browse files
committed
perf: Replace Vec w/ static arrays for act buffers
After reviewing lightningdevkit#494 there was design feedback on the use of Vec vs. arrays in this layer of the code for fragmentation reasons. To get ahead of the same issues in this new state machine and to limit the behavior changes from master, remove Vec from the state machine in favor of thin wrappers around fixed-size arrays. This patch reintroduces the Act object (a thin wrapper around fixed size arrays) with some convenience features to make them a bit easier to pass around and build. The Handshake code is still note Vec-clean as the encryption code uses Vecs during encryption, but it is one step closer to passing back slices to the peer_handler.
1 parent 81bf5fb commit 9e79003

File tree

3 files changed

+236
-62
lines changed

3 files changed

+236
-62
lines changed
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
//! Helper library for working with NOISE handshake Act data. Contains utilities for passing Act
2+
//! objects around and building them from received data.
3+
//! Act objects are thin wrappers about raw arrays for stack-based processing as well as convenient
4+
//! coercion to slices for flexible use by the higher-level modules.
5+
6+
use std::{cmp, ops};
7+
8+
pub const ACT_ONE_TWO_LENGTH: usize = 50;
9+
pub const ACT_THREE_LENGTH: usize = 66;
10+
pub const EMPTY_ACT_ONE: ActOne = [0; ACT_ONE_TWO_LENGTH];
11+
pub const EMPTY_ACT_TWO: ActTwo = EMPTY_ACT_ONE;
12+
pub const EMPTY_ACT_THREE: ActThree = [0; ACT_THREE_LENGTH];
13+
type ActOne = [u8; ACT_ONE_TWO_LENGTH];
14+
type ActTwo = ActOne;
15+
type ActThree = [u8; ACT_THREE_LENGTH];
16+
17+
/// Wrapper for any act message
18+
pub(super) enum Act {
19+
One(ActOne),
20+
Two(ActTwo),
21+
Three(ActThree)
22+
}
23+
24+
impl Act {
25+
/// Returns the size of the underlying array
26+
fn len(&self) -> usize {
27+
self.as_ref().len()
28+
}
29+
}
30+
31+
impl From<ActBuilder> for Act {
32+
/// Convert a finished ActBuilder into an Act
33+
fn from(act_builder: ActBuilder) -> Self {
34+
assert!(act_builder.is_finished());
35+
act_builder.partial_act
36+
}
37+
}
38+
39+
impl ops::Deref for Act {
40+
type Target = [u8];
41+
42+
/// Allows automatic coercion to slices in function calls
43+
/// &Act -> &[u8]
44+
fn deref(&self) -> &Self::Target {
45+
match self {
46+
&Act::One(ref act) => {
47+
act
48+
}
49+
&Act::Two(ref act) => {
50+
act
51+
}
52+
&Act::Three(ref act) => {
53+
act
54+
}
55+
}
56+
}
57+
}
58+
59+
impl AsRef<[u8]> for Act {
60+
/// Allow convenient exposure of the underlying array through as_ref()
61+
/// Act.as_ref() -> &[u8]
62+
fn as_ref(&self) -> &[u8] {
63+
&self
64+
}
65+
}
66+
67+
// Simple fill implementation for both almost-identical structs to deduplicate code
68+
// $act: Act[One|Two|Three], $input: &[u8]; returns &[u8] of remaining input that was not processed
69+
macro_rules! fill_impl {
70+
($act:expr, $write_pos:expr, $input:expr) => {{
71+
let fill_amount = cmp::min($act.len() - $write_pos, $input.len());
72+
73+
$act[$write_pos..$write_pos + fill_amount].copy_from_slice(&$input[..fill_amount]);
74+
75+
$write_pos += fill_amount;
76+
&$input[fill_amount..]
77+
}}
78+
}
79+
80+
/// Light wrapper around an Act that allows multiple fill() calls before finally
81+
/// converting to an Act via Act::from(act_builder). Handles all of the bookkeeping
82+
/// and edge cases of the array fill
83+
pub(super) struct ActBuilder {
84+
partial_act: Act,
85+
write_pos: usize
86+
}
87+
88+
impl ActBuilder {
89+
/// Returns a new ActBuilder for Act::One
90+
pub(super) fn new(empty_act: Act) -> Self {
91+
Self {
92+
partial_act: empty_act,
93+
write_pos: 0
94+
}
95+
}
96+
97+
/// Fills the Act with bytes from input and returns the unprocessed bytes
98+
pub(super) fn fill<'a>(&mut self, input: &'a [u8]) -> &'a [u8] {
99+
match &mut self.partial_act {
100+
&mut Act::One(ref mut act) => {
101+
fill_impl!(act, self.write_pos, input)
102+
}
103+
&mut Act::Two(ref mut act) => {
104+
fill_impl!(act, self.write_pos, input)
105+
}
106+
&mut Act::Three(ref mut act) => {
107+
fill_impl!(act, self.write_pos, input)
108+
}
109+
}
110+
}
111+
112+
/// Returns true if the Act is finished building (enough bytes via fill())
113+
pub(super) fn is_finished(&self) -> bool {
114+
self.write_pos == self.partial_act.len()
115+
}
116+
}
117+
118+
#[cfg(test)]
119+
mod tests {
120+
use super::*;
121+
122+
// Test bookkeeping of partial fill
123+
#[test]
124+
fn partial_fill() {
125+
let mut builder = ActBuilder::new(Act::One(EMPTY_ACT_ONE));
126+
127+
let remaining = builder.fill(&[1, 2, 3]);
128+
assert_eq!(builder.partial_act.len(), ACT_ONE_TWO_LENGTH);
129+
assert_eq!(builder.write_pos, 3);
130+
assert!(!builder.is_finished());
131+
assert_eq!(remaining, &[]);
132+
}
133+
134+
// Test bookkeeping of exact fill
135+
#[test]
136+
fn exact_fill() {
137+
let mut builder = ActBuilder::new(Act::One(EMPTY_ACT_ONE));
138+
139+
let input = [0; 50];
140+
let remaining = builder.fill(&input);
141+
assert_eq!(builder.partial_act.len(), ACT_ONE_TWO_LENGTH);
142+
assert_eq!(builder.write_pos, ACT_ONE_TWO_LENGTH);
143+
assert!(builder.is_finished());
144+
assert_eq!(Act::from(builder).as_ref(), &input[..]);
145+
assert_eq!(remaining, &[]);
146+
}
147+
148+
// Test bookkeeping of overfill
149+
#[test]
150+
fn over_fill() {
151+
let mut builder = ActBuilder::new(Act::One(EMPTY_ACT_ONE));
152+
153+
let input = [0; 51];
154+
let remaining = builder.fill(&input);
155+
156+
assert_eq!(builder.partial_act.len(), ACT_ONE_TWO_LENGTH);
157+
assert_eq!(builder.write_pos, ACT_ONE_TWO_LENGTH);
158+
assert!(builder.is_finished());
159+
assert_eq!(Act::from(builder).as_ref(), &input[..50]);
160+
assert_eq!(remaining, &[0]);
161+
}
162+
163+
// Converting an unfinished ActBuilder panics
164+
#[test]
165+
#[should_panic(expected="as")]
166+
fn convert_not_finished_panics() {
167+
let builder = ActBuilder::new(Act::One(EMPTY_ACT_ONE));
168+
let _should_panic = Act::from(builder);
169+
}
170+
}
171+

lightning/src/ln/peers/handshake/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use bitcoin::secp256k1::{PublicKey, SecretKey};
77
use ln::peers::conduit::Conduit;
88
use ln::peers::handshake::states::{HandshakeState, IHandshakeState};
99

10+
mod acts;
1011
mod states;
1112

1213
/// Object for managing handshakes.
@@ -46,7 +47,7 @@ impl PeerHandshake {
4647
self.state = Some(next_state);
4748

4849
self.ready_for_process = true;
49-
response_vec_opt.unwrap()
50+
response_vec_opt.unwrap().to_vec()
5051
}
5152

5253
/// Process act dynamically
@@ -61,7 +62,12 @@ impl PeerHandshake {
6162
assert!(self.ready_for_process);
6263
let cur_state = self.state.take().unwrap();
6364

64-
let (act_opt, mut next_state) = cur_state.next(input)?;
65+
// Convert the Act to a Vec before passing it back. Next patch removes this in favor
66+
// of passing back a slice.
67+
let (act_opt, mut next_state) = match cur_state.next(input)? {
68+
(Some(act), next_state) => (Some(act.to_vec()), next_state),
69+
(None, next_state) => (None, next_state)
70+
};
6571

6672
let result = match next_state {
6773
HandshakeState::Complete(ref mut conduit_and_pubkey) => {

0 commit comments

Comments
 (0)