Skip to content

Commit c7f99c7

Browse files
povilasbustulation
authored andcommitted
More tests (#94)
* fix/docs: fix mio types we use * test/udp_rendezvous_server: add integration test; also exposed UdpRendezvousClient (not as a crate API though, only for internal modules for testing). * make UdpRendezvousServer return it's listen address (in case someone specifies port 0 for it) * fixed UdpRendezvousServer on Windows where the client tried to connect with the server using 0.0.0.0 IP address. * fix/ci: skip rustfmt on Windows We hit the rustfmt bug: rust-lang/rustfmt#1873 . Currently, rustfmt fails to format when path attribute has relative paths. Usually, our code does not have too much windows specific code and in this case there's no such code at the moment. So, it's the easiest fix is to disable rustfmt on Windows for now hoping for the fix in the future.
1 parent 93290d0 commit c7f99c7

File tree

7 files changed

+300
-226
lines changed

7 files changed

+300
-226
lines changed

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ before_script:
3232
fi
3333
script:
3434
- set -x;
35-
cargo fmt -- --check &&
3635
cargo test --verbose --release
3736
- if [ "${TRAVIS_OS_NAME}" = linux ]; then
3837
(
3938
set -x;
39+
cargo fmt -- --check &&
4040
cargo clippy --verbose --release &&
4141
cargo clippy --verbose --release --profile=test ;
4242
)

src/lib.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -277,11 +277,15 @@ extern crate serde_derive;
277277
extern crate unwrap;
278278

279279
extern crate bincode;
280+
#[cfg(test)]
281+
extern crate maidsafe_utilities;
280282
extern crate mio;
281283
extern crate mio_extras;
282284
extern crate net2;
283285
extern crate rand;
284286
extern crate rust_sodium as sodium;
287+
#[cfg(test)]
288+
extern crate serde_json;
285289
extern crate socket_collection;
286290

287291
use bincode::{deserialize, serialize, Infinite};
@@ -300,6 +304,8 @@ mod error;
300304
mod hole_punch;
301305
mod queued_notifier;
302306
mod tcp;
307+
#[cfg(test)]
308+
mod test_utils;
303309
mod udp;
304310

305311
pub use config::Config;
@@ -346,7 +352,7 @@ impl NatTimer {
346352
/// A message that can be sent to the event loop to perform an action.
347353
///
348354
/// This can be used to send actions from a thread outside the event loop too if sent via
349-
/// [`mio::channel::Sender`][0].
355+
/// [`mio_extras::channel::Sender`][0].
350356
///
351357
/// [0]: http://rust-doc.s3-website-us-east-1.amazonaws.com/mio/master/mio/channel
352358
pub struct NatMsg(Box<FnMut(&mut Interface, &Poll) + Send + 'static>);
@@ -408,8 +414,8 @@ pub trait Interface {
408414
fn remove_state(&mut self, token: Token) -> Option<Rc<RefCell<NatState>>>;
409415
/// Return the state (without removing - just a query) associated with the `token`
410416
fn state(&mut self, token: Token) -> Option<Rc<RefCell<NatState>>>;
411-
/// Set timeout. User code is expected to have a `mio::timer::Timer<NatTimer>` on which the
412-
/// timeout can be set.
417+
/// Set timeout. User code is expected to have a `mio_extras::timer::Timer<NatTimer>` on which
418+
/// the timeout can be set.
413419
fn set_timeout(&mut self, duration: Duration, timer_detail: NatTimer) -> Timeout;
414420
/// Cancel the timout
415421
fn cancel_timeout(&mut self, timeout: &Timeout) -> Option<NatTimer>;

src/test_utils.rs

+203
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
use super::{Config, Interface, NatMsg, NatState, NatTimer};
2+
use maidsafe_utilities::thread::{self, Joiner};
3+
use mio::{Events, Poll, PollOpt, Ready, Token};
4+
use mio_extras::channel;
5+
use mio_extras::channel::Sender;
6+
use mio_extras::timer::{Timeout, Timer};
7+
use serde_json;
8+
use sodium::crypto::box_;
9+
use std::any::Any;
10+
use std::cell::RefCell;
11+
use std::collections::hash_map::Entry;
12+
use std::collections::HashMap;
13+
use std::fs::File;
14+
use std::io::Read;
15+
use std::rc::Rc;
16+
use std::time::Duration;
17+
18+
/// Simplified state machine implementation for tests.
19+
pub struct StateMachine {
20+
pub nat_states: HashMap<Token, Rc<RefCell<NatState>>>,
21+
pub timer: Timer<NatTimer>,
22+
pub token: usize,
23+
pub config: Config,
24+
pub enc_pk: box_::PublicKey,
25+
pub enc_sk: box_::SecretKey,
26+
pub tx: Sender<NatMsg>,
27+
}
28+
29+
impl StateMachine {
30+
pub fn handle_nat_timer(&mut self, poll: &Poll) {
31+
while let Some(nat_timer) = self.timer.poll() {
32+
if let Some(nat_state) = self.state(nat_timer.associated_nat_state) {
33+
nat_state
34+
.borrow_mut()
35+
.timeout(self, poll, nat_timer.timer_id);
36+
}
37+
}
38+
}
39+
40+
pub fn handle_readiness(&mut self, poll: &Poll, token: Token, kind: Ready) {
41+
if let Some(nat_state) = self.state(token) {
42+
return nat_state.borrow_mut().ready(self, poll, kind);
43+
}
44+
}
45+
}
46+
47+
impl Interface for StateMachine {
48+
fn insert_state(
49+
&mut self,
50+
token: Token,
51+
state: Rc<RefCell<NatState>>,
52+
) -> Result<(), (Rc<RefCell<NatState>>, String)> {
53+
if let Entry::Vacant(ve) = self.nat_states.entry(token) {
54+
let _ = ve.insert(state);
55+
Ok(())
56+
} else {
57+
Err((state, "Token is already mapped".to_string()))
58+
}
59+
}
60+
61+
fn remove_state(&mut self, token: Token) -> Option<Rc<RefCell<NatState>>> {
62+
self.nat_states.remove(&token)
63+
}
64+
65+
fn state(&mut self, token: Token) -> Option<Rc<RefCell<NatState>>> {
66+
self.nat_states.get(&token).cloned()
67+
}
68+
69+
fn set_timeout(&mut self, duration: Duration, timer_detail: NatTimer) -> Timeout {
70+
self.timer.set_timeout(duration, timer_detail)
71+
}
72+
73+
fn cancel_timeout(&mut self, timeout: &Timeout) -> Option<NatTimer> {
74+
self.timer.cancel_timeout(timeout)
75+
}
76+
77+
fn new_token(&mut self) -> Token {
78+
self.token += 1;
79+
Token(self.token)
80+
}
81+
82+
fn config(&self) -> &Config {
83+
&self.config
84+
}
85+
86+
fn enc_pk(&self) -> &box_::PublicKey {
87+
&self.enc_pk
88+
}
89+
90+
fn enc_sk(&self) -> &box_::SecretKey {
91+
&self.enc_sk
92+
}
93+
94+
fn sender(&self) -> &Sender<NatMsg> {
95+
&self.tx
96+
}
97+
98+
fn as_any(&mut self) -> &mut Any {
99+
self
100+
}
101+
}
102+
103+
/// Spawn testing event loop in a separate thread and return a handle to control it.
104+
pub fn spawn_event_loop(config: Config) -> EventLoop {
105+
let (done_tx, done_rx) = channel::channel();
106+
let (nat_tx, nat_rx) = channel::channel();
107+
let nat_tx2 = nat_tx.clone();
108+
109+
let j = thread::named("Event-Loop", move || {
110+
const TIMER_TOKEN: usize = 0;
111+
const DONE_RX_TOKEN: usize = TIMER_TOKEN + 1;
112+
const NAT_RX_TOKEN: usize = DONE_RX_TOKEN + 1;
113+
114+
let poll = unwrap!(Poll::new());
115+
116+
let (enc_pk, enc_sk) = box_::gen_keypair();
117+
let timer = Timer::default();
118+
119+
unwrap!(poll.register(
120+
&timer,
121+
Token(TIMER_TOKEN),
122+
Ready::readable(),
123+
PollOpt::edge(),
124+
));
125+
unwrap!(poll.register(
126+
&done_rx,
127+
Token(DONE_RX_TOKEN),
128+
Ready::readable(),
129+
PollOpt::edge(),
130+
));
131+
unwrap!(poll.register(
132+
&nat_rx,
133+
Token(NAT_RX_TOKEN),
134+
Ready::readable(),
135+
PollOpt::edge(),
136+
));
137+
138+
let mut sm = StateMachine {
139+
nat_states: HashMap::with_capacity(10),
140+
timer,
141+
token: NAT_RX_TOKEN + 1,
142+
config,
143+
enc_pk,
144+
enc_sk,
145+
tx: nat_tx,
146+
};
147+
148+
let mut events = Events::with_capacity(1024);
149+
150+
'event_loop: loop {
151+
unwrap!(poll.poll(&mut events, None));
152+
153+
for event in events.iter() {
154+
match event.token() {
155+
Token(TIMER_TOKEN) => {
156+
assert!(event.readiness().is_readable());
157+
sm.handle_nat_timer(&poll);
158+
}
159+
Token(DONE_RX_TOKEN) => {
160+
assert!(event.readiness().is_readable());
161+
unwrap!(done_rx.try_recv());
162+
break 'event_loop;
163+
}
164+
Token(NAT_RX_TOKEN) => {
165+
assert!(event.readiness().is_readable());
166+
while let Ok(f) = nat_rx.try_recv() {
167+
f.invoke(&mut sm, &poll);
168+
}
169+
}
170+
t => sm.handle_readiness(&poll, t, event.readiness()),
171+
}
172+
}
173+
}
174+
});
175+
176+
EventLoop {
177+
nat_tx: nat_tx2,
178+
done_tx,
179+
_j: j,
180+
}
181+
}
182+
183+
/// Handle to event loop running in a separate thread.
184+
pub struct EventLoop {
185+
pub nat_tx: Sender<NatMsg>,
186+
pub done_tx: Sender<()>,
187+
_j: Joiner,
188+
}
189+
190+
impl Drop for EventLoop {
191+
fn drop(&mut self) {
192+
unwrap!(self.done_tx.send(()));
193+
}
194+
}
195+
196+
/// Read p2p config from json file.
197+
#[allow(unused)]
198+
pub fn read_config(path: &str) -> Config {
199+
let mut file = unwrap!(File::open(path));
200+
let mut content = String::new();
201+
unwrap!(file.read_to_string(&mut content));
202+
unwrap!(serde_json::from_str(&content))
203+
}

src/udp/hole_punch/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use self::puncher::Puncher;
2-
use self::rendezvous_client::UdpRendezvousClient;
2+
pub use self::rendezvous_client::UdpRendezvousClient;
33
use mio::Poll;
44
use mio::Token;
55
use socket_collection::UdpSock;

src/udp/hole_punch/rendezvous_client.rs

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use {Interface, NatError, NatState, NatType};
1313

1414
pub type Finish = Box<FnMut(&mut Interface, &Poll, Token, NatType, ::Res<(UdpSock, SocketAddr)>)>;
1515

16+
/// UDP rendezvous client that queries the server for it's public endpoint - IP:port.
1617
pub struct UdpRendezvousClient {
1718
sock: UdpSock,
1819
token: Token,
@@ -25,6 +26,7 @@ pub struct UdpRendezvousClient {
2526
}
2627

2728
impl UdpRendezvousClient {
29+
/// Starts sending queries to multiple servers. Servers are retrieved from `ifc.config()`.
2830
pub fn start(ifc: &mut Interface, poll: &Poll, sock: UdpSock, f: Finish) -> ::Res<Token> {
2931
let token = ifc.new_token();
3032
let mut servers = ifc.config().remote_udp_rendezvous_servers.clone();

src/udp/rendezvous_server.rs

+73-3
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,22 @@ pub struct UdpRendezvousServer {
2626

2727
impl UdpRendezvousServer {
2828
/// Boot the UDP Rendezvous server. This should normally be called only once.
29-
pub fn start(ifc: &mut Interface, poll: &Poll) -> ::Res<Token> {
29+
/// Uses the bind port specified by `Interface::config()`. If port is 0, OS chooses a random
30+
/// available port, which you can find out by checking the return value.
31+
///
32+
/// # Returns
33+
///
34+
/// A tuple of mio token associated with the rendezvous server and local listen address.
35+
pub fn start(ifc: &mut Interface, poll: &Poll) -> ::Res<(Token, SocketAddr)> {
3036
let port = ifc
3137
.config()
3238
.udp_rendezvous_port
3339
.unwrap_or(UDP_RENDEZVOUS_PORT);
3440
let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), port));
3541
let sock = UdpSock::bind(&addr)?;
42+
let server_addr = sock.local_addr()?;
3643

3744
let token = ifc.new_token();
38-
3945
poll.register(
4046
&sock,
4147
token,
@@ -54,7 +60,7 @@ impl UdpRendezvousServer {
5460
server.borrow_mut().terminate(ifc, poll);
5561
Err(NatError::UdpRendezvousServerStartFailed)
5662
} else {
57-
Ok(token)
63+
Ok((token, server_addr))
5864
}
5965
}
6066

@@ -117,3 +123,67 @@ impl NatState for UdpRendezvousServer {
117123
self
118124
}
119125
}
126+
127+
#[cfg(test)]
128+
mod tests {
129+
use super::*;
130+
use std::net::SocketAddr;
131+
use std::sync::mpsc;
132+
use test_utils::spawn_event_loop;
133+
use udp::hole_punch::UdpRendezvousClient;
134+
use {Config, NatMsg};
135+
136+
/// Creates config for tests with all values zeroed.
137+
fn p2p_test_cfg() -> Config {
138+
Config {
139+
rendezvous_timeout_sec: None,
140+
hole_punch_timeout_sec: None,
141+
hole_punch_wait_for_other: None,
142+
udp_rendezvous_port: None,
143+
tcp_rendezvous_port: None,
144+
remote_udp_rendezvous_servers: Vec::new(),
145+
remote_tcp_rendezvous_servers: Vec::new(),
146+
udp_hole_punchers: Vec::new(),
147+
}
148+
}
149+
150+
#[test]
151+
fn it_responds_with_client_address() {
152+
let mut config = p2p_test_cfg();
153+
config.udp_rendezvous_port = Some(0);
154+
let server_el = spawn_event_loop(config);
155+
let (server_port_tx, server_port_rx) = mpsc::channel();
156+
unwrap!(server_el.nat_tx.send(NatMsg::new(move |ifc, poll| {
157+
let (_token, addr) = unwrap!(UdpRendezvousServer::start(ifc, poll));
158+
unwrap!(server_port_tx.send(addr.port()));
159+
})));
160+
161+
let server_port = unwrap!(server_port_rx.recv());
162+
let server_addr =
163+
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), server_port));
164+
let (addr_tx, addr_rx) = mpsc::channel();
165+
let mut config = p2p_test_cfg();
166+
config.remote_udp_rendezvous_servers = vec![server_addr];
167+
let client_el = spawn_event_loop(config);
168+
let addr = unwrap!("127.0.0.1:0".parse());
169+
let sock = unwrap!(UdpSock::bind(&addr));
170+
let exp_client_addr = unwrap!(sock.local_addr());
171+
172+
unwrap!(client_el.nat_tx.send(NatMsg::new(move |ifc, poll| {
173+
let on_done = Box::new(
174+
move |_ifc: &mut Interface,
175+
_poll: &Poll,
176+
_child,
177+
_nat_type,
178+
res: ::Res<(UdpSock, SocketAddr)>| {
179+
let client_addr = unwrap!(res).1;
180+
unwrap!(addr_tx.send(client_addr));
181+
},
182+
);
183+
let _ = unwrap!(UdpRendezvousClient::start(ifc, poll, sock, on_done));
184+
})));
185+
186+
let client_addr = unwrap!(addr_rx.recv());
187+
assert_eq!(client_addr, exp_client_addr);
188+
}
189+
}

0 commit comments

Comments
 (0)