Skip to content

Commit 3a6f887

Browse files
committed
refactor: Abstract Transport layer and test it
Start moving the code that implements the Bolt #8 transport layer into a separate testable module. This patch makes heavy use of Rust's supported mocking pattern to create test doubles and leverage them to write simple unit tests for the public API of Transport.
1 parent c090cb2 commit 3a6f887

File tree

7 files changed

+664
-347
lines changed

7 files changed

+664
-347
lines changed

lightning/src/ln/peers/handler.rs

Lines changed: 159 additions & 220 deletions
Large diffs are not rendered by default.

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

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,32 @@
55
use bitcoin::secp256k1::{PublicKey, SecretKey};
66

77
use ln::peers::conduit::Conduit;
8-
use ln::peers::handshake::states::{HandshakeState, IHandshakeState};
8+
use ln::peers::handshake::acts::Act;
9+
use ln::peers::handshake::states::HandshakeState;
10+
use ln::peers::transport::IPeerHandshake;
911

1012
mod acts;
1113
mod states;
1214

15+
/// Interface used by PeerHandshake to interact with NOISE state machine.
16+
/// State may transition to the same state in the event there are not yet enough bytes to move
17+
/// forward with the handshake.
18+
trait IHandshakeState {
19+
/// Returns the next HandshakeState after processing the input bytes
20+
fn next(self, input: &[u8]) -> Result<(Option<Act>, HandshakeState), String>;
21+
}
22+
1323
/// Object for managing handshakes.
1424
/// Currently requires explicit ephemeral private key specification.
1525
pub struct PeerHandshake {
1626
state: Option<HandshakeState>,
1727
ready_to_process: bool,
1828
}
1929

20-
impl PeerHandshake {
30+
impl IPeerHandshake for PeerHandshake {
2131
/// Instantiate a handshake given the peer's static public key. The ephemeral private key MUST
2232
/// generate a new session with strong cryptographic randomness.
23-
pub fn new_outbound(initiator_static_private_key: &SecretKey, responder_static_public_key: &PublicKey, initiator_ephemeral_private_key: &SecretKey) -> Self {
33+
fn new_outbound(initiator_static_private_key: &SecretKey, responder_static_public_key: &PublicKey, initiator_ephemeral_private_key: &SecretKey) -> Self {
2434
let state = HandshakeState::new_initiator(initiator_static_private_key, responder_static_public_key, initiator_ephemeral_private_key);
2535

2636
Self {
@@ -29,16 +39,8 @@ impl PeerHandshake {
2939
}
3040
}
3141

32-
/// Instantiate a handshake in anticipation of a peer's first handshake act
33-
pub fn new_inbound(responder_static_private_key: &SecretKey, responder_ephemeral_private_key: &SecretKey) -> Self {
34-
Self {
35-
state: Some(HandshakeState::new_responder(responder_static_private_key, responder_ephemeral_private_key)),
36-
ready_to_process: true,
37-
}
38-
}
39-
4042
/// Initializes the outbound handshake and provides the initial bytes to send to the responder
41-
pub fn set_up_outbound(&mut self) -> Vec<u8> {
43+
fn set_up_outbound(&mut self) -> Vec<u8> {
4244
assert!(!self.ready_to_process);
4345
self.ready_to_process = true;
4446

@@ -49,6 +51,14 @@ impl PeerHandshake {
4951
response_vec_option.unwrap()
5052
}
5153

54+
/// Instantiate a new handshake in anticipation of a peer's first handshake act
55+
fn new_inbound(responder_static_private_key: &SecretKey, responder_ephemeral_private_key: &SecretKey) -> Self {
56+
Self {
57+
state: Some(HandshakeState::new_responder(responder_static_private_key, responder_ephemeral_private_key)),
58+
ready_to_process: true,
59+
}
60+
}
61+
5262
/// Process act dynamically
5363
/// # Arguments
5464
/// `input`: Byte slice received from peer as part of the handshake protocol
@@ -57,7 +67,7 @@ impl PeerHandshake {
5767
/// Returns a tuple with the following components:
5868
/// `.0`: Byte vector containing the next act to send back to the peer per the handshake protocol
5969
/// `.1`: Some(Conduit, PublicKey) if the handshake was just processed to completion and messages can now be encrypted and decrypted
60-
pub fn process_act(&mut self, input: &[u8]) -> Result<(Option<Vec<u8>>, Option<(Conduit, PublicKey)>), String> {
70+
fn process_act(&mut self, input: &[u8]) -> Result<(Option<Vec<u8>>, Option<(Conduit, PublicKey)>), String> {
6171
assert!(self.ready_to_process);
6272
let cur_state = self.state.take().unwrap();
6373

@@ -120,15 +130,6 @@ mod test {
120130
}
121131
}
122132

123-
macro_rules! assert_matches {
124-
($e:expr, $state_match:pat) => {
125-
match $e {
126-
$state_match => (),
127-
_ => panic!()
128-
}
129-
}
130-
}
131-
132133
macro_rules! do_process_act_or_panic {
133134
($handshake:expr, $input:expr) => {
134135
$handshake.process_act($input).unwrap().0.unwrap()

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use bitcoin::secp256k1::{SecretKey, PublicKey};
77
use ln::peers::{chacha, hkdf5869rfc};
88
use ln::peers::conduit::{Conduit, SymmetricKey};
99
use ln::peers::handshake::acts::{Act, ActBuilder, ACT_ONE_LENGTH, ACT_TWO_LENGTH, ACT_THREE_LENGTH, EMPTY_ACT_ONE, EMPTY_ACT_TWO, EMPTY_ACT_THREE};
10+
use ln::peers::handshake::IHandshakeState;
1011

1112
// Alias type to help differentiate between temporary key and chaining key when passing bytes around
1213
type ChainingKey = [u8; 32];
@@ -30,13 +31,6 @@ pub(super) enum HandshakeState {
3031
Complete(Option<(Conduit, PublicKey)>),
3132
}
3233

33-
// Trait for all individual states to implement that ensure HandshakeState::next() can
34-
// delegate to a common function signature. May transition to the same state in the event there are
35-
// not yet enough bytes to move forward with the handshake.
36-
pub(super) trait IHandshakeState {
37-
fn next(self, input: &[u8]) -> Result<(Option<Act>, HandshakeState), String>;
38-
}
39-
4034
// Enum dispatch for state machine. Single public interface can statically dispatch to all states
4135
impl HandshakeState {
4236
pub(super) fn new_initiator(initiator_static_private_key: &SecretKey, responder_static_public_key: &PublicKey, initiator_ephemeral_private_key: &SecretKey) -> Self {

lightning/src/ln/peers/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@
33
//! When a handshake completes, it returns an instance of Conduit.
44
//! Conduit enables message encryption and decryption, and automatically handles key rotation.
55
6+
#[cfg(test)]
7+
#[macro_use]
8+
mod test_util;
9+
610
mod chacha;
711
pub mod handler;
812
mod hkdf5869rfc;
913
mod outbound_buffer;
14+
mod transport;
1015

1116
#[cfg(feature = "fuzztarget")]
1217
pub mod conduit;

lightning/src/ln/peers/outbound_buffer.rs

Lines changed: 1 addition & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -83,104 +83,7 @@ impl OutboundBuffer {
8383
#[cfg(test)]
8484
mod tests {
8585
use super::*;
86-
use std::rc::Rc;
87-
use std::cell::RefCell;
88-
use std::hash::Hash;
89-
use std::cmp;
90-
91-
/// Mock implementation of the SocketDescriptor trait that can be used in tests to finely control
92-
/// the send_data() behavior.
93-
///
94-
///Additionally, records the actual calls to send_data() for later validation.
95-
#[derive(Debug, Eq)]
96-
struct SocketDescriptorMock {
97-
/// If true, all send_data() calls will succeed
98-
unbounded: Rc<RefCell<bool>>,
99-
100-
/// Amount of free space in the descriptor for send_data() bytes
101-
free_space: Rc<RefCell<usize>>,
102-
103-
/// Vector of arguments and return values to send_data() used for validation
104-
send_recording: Rc<RefCell<Vec<(Vec<u8>, bool)>>>,
105-
}
106-
107-
impl SocketDescriptorMock {
108-
/// Basic unbounded implementation where send_data() will always succeed
109-
fn new() -> Self {
110-
Self {
111-
unbounded: Rc::new(RefCell::new(true)),
112-
send_recording: Rc::new(RefCell::new(Vec::new())),
113-
free_space: Rc::new(RefCell::new(0))
114-
}
115-
}
116-
117-
/// Used for tests that want to return partial sends after a certain amount of data is sent through send_data()
118-
fn with_fixed_size(limit: usize) -> Self {
119-
let mut descriptor = Self::new();
120-
descriptor.unbounded = Rc::new(RefCell::new(false));
121-
descriptor.free_space = Rc::new(RefCell::new(limit));
122-
123-
descriptor
124-
}
125-
126-
/// Standard Mock api to verify actual vs. expected calls
127-
fn assert_called_with(&self, expectation: Vec<(Vec<u8>, bool)>) {
128-
assert_eq!(expectation.as_slice(), self.send_recording.borrow().as_slice())
129-
}
130-
131-
/// Allow future send_data() calls to succeed for the next added_room bytes. Not valid for
132-
/// unbounded mock descriptors
133-
fn make_room(&mut self, added_room: usize) {
134-
assert!(!*self.unbounded.borrow());
135-
let mut free_space = self.free_space.borrow_mut();
136-
137-
*free_space += added_room;
138-
}
139-
}
140-
141-
impl SocketDescriptor for SocketDescriptorMock {
142-
fn send_data(&mut self, data: &[u8], resume_read: bool) -> usize {
143-
self.send_recording.borrow_mut().push((data.to_vec(), resume_read));
144-
145-
let mut free_space = self.free_space.borrow_mut();
146-
147-
// Unbounded just flush everything
148-
return if *self.unbounded.borrow() {
149-
data.len()
150-
}
151-
// Bounded flush up to the free_space limit
152-
else {
153-
let write_len = cmp::min(data.len(), *free_space);
154-
*free_space -= write_len;
155-
write_len
156-
}
157-
}
158-
159-
fn disconnect_socket(&mut self) {
160-
unimplemented!()
161-
}
162-
}
163-
164-
impl Clone for SocketDescriptorMock {
165-
fn clone(&self) -> Self {
166-
Self {
167-
unbounded: self.unbounded.clone(),
168-
send_recording: self.send_recording.clone(),
169-
free_space: self.free_space.clone()
170-
}
171-
}
172-
}
173-
174-
impl PartialEq for SocketDescriptorMock {
175-
fn eq(&self, o: &Self) -> bool {
176-
self.send_recording == o.send_recording
177-
}
178-
}
179-
impl Hash for SocketDescriptorMock {
180-
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
181-
self.send_recording.borrow().hash(state);
182-
}
183-
}
86+
use ln::peers::test_util::*;
18487

18588
// Test that a try_flush_one() call with no queued data doesn't write anything
18689
#[test]

0 commit comments

Comments
 (0)