Proper server and protocol event loop. Channels for shutdown and ping/pong trivial roundtrip. Working test.

This commit is contained in:
Ignotus Peverell 2016-10-30 18:23:52 -07:00
parent 2efa8ce706
commit ee6fcab8db
No known key found for this signature in database
GPG key ID: 99CD25F39F8F8211
8 changed files with 268 additions and 156 deletions

View file

@ -79,7 +79,7 @@ impl Handshake {
}); });
} }
let peer_info = PeerInfo{ let peer_info = PeerInfo {
capabilities: shake.capabilities, capabilities: shake.capabilities,
user_agent: shake.user_agent, user_agent: shake.user_agent,
addr: receiver_addr, addr: receiver_addr,
@ -119,7 +119,7 @@ impl Handshake {
} }
// all good, keep peer info // all good, keep peer info
let peer_info = PeerInfo{ let peer_info = PeerInfo {
capabilities: hand.capabilities, capabilities: hand.capabilities,
user_agent: hand.user_agent, user_agent: hand.user_agent,
addr: conn.peer_addr().unwrap(), addr: conn.peer_addr().unwrap(),

View file

@ -39,5 +39,5 @@ mod protocol;
mod server; mod server;
mod peer; mod peer;
pub use server::Server; pub use server::{Server, DummyAdapter};
pub use server::DEFAULT_LISTEN_ADDR; pub use server::DEFAULT_LISTEN_ADDR;

View file

@ -36,13 +36,13 @@ pub enum ErrCodes {
/// Types of messages /// Types of messages
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum Type { pub enum Type {
ERROR, Error,
HAND, Hand,
SHAKE, Shake,
PING, Ping,
PONG, Pong,
GETPEERADDRS, GetPeerAddrs,
PEERADDRS, PeerAddrs,
/// Never used over the network but to detect unrecognized types. /// Never used over the network but to detect unrecognized types.
MaxMsgType, MaxMsgType,
} }
@ -50,10 +50,17 @@ pub enum Type {
/// Header of any protocol message, used to identify incoming messages. /// Header of any protocol message, used to identify incoming messages.
pub struct MsgHeader { pub struct MsgHeader {
magic: [u8; 2], magic: [u8; 2],
msg_type: Type, pub msg_type: Type,
} }
impl MsgHeader { impl MsgHeader {
pub fn new(msg_type: Type) -> MsgHeader {
MsgHeader {
magic: MAGIC,
msg_type: msg_type,
}
}
pub fn acceptable(&self) -> bool { pub fn acceptable(&self) -> bool {
(self.msg_type as u8) < (Type::MaxMsgType as u8) (self.msg_type as u8) < (Type::MaxMsgType as u8)
} }
@ -74,20 +81,20 @@ impl Readable<MsgHeader> for MsgHeader {
try!(reader.expect_u8(MAGIC[0])); try!(reader.expect_u8(MAGIC[0]));
try!(reader.expect_u8(MAGIC[1])); try!(reader.expect_u8(MAGIC[1]));
let t = try!(reader.read_u8()); let t = try!(reader.read_u8());
if t < (Type::MaxMsgType as u8) { if t >= (Type::MaxMsgType as u8) {
return Err(ser::Error::CorruptedData); return Err(ser::Error::CorruptedData);
} }
Ok(MsgHeader { Ok(MsgHeader {
magic: MAGIC, magic: MAGIC,
msg_type: match t { msg_type: match t {
// TODO this is rather ugly, think of a better way // TODO this is rather ugly, think of a better way
0 => Type::ERROR, 0 => Type::Error,
1 => Type::HAND, 1 => Type::Hand,
2 => Type::SHAKE, 2 => Type::Shake,
3 => Type::PING, 3 => Type::Ping,
4 => Type::PONG, 4 => Type::Pong,
5 => Type::GETPEERADDRS, 5 => Type::GetPeerAddrs,
6 => Type::PEERADDRS, 6 => Type::PeerAddrs,
_ => panic!(), _ => panic!(),
}, },
}) })

View file

@ -16,7 +16,6 @@ use mioco::tcp::TcpStream;
use core::ser::Error; use core::ser::Error;
use handshake::Handshake; use handshake::Handshake;
use msg::*;
use types::*; use types::*;
pub struct Peer { pub struct Peer {
@ -28,10 +27,9 @@ unsafe impl Sync for Peer {}
unsafe impl Send for Peer {} unsafe impl Send for Peer {}
impl Peer { impl Peer {
pub fn connect(conn: TcpStream, hs: &Handshake) -> Result<Peer, Error> { pub fn connect(conn: TcpStream, hs: &Handshake) -> Result<Peer, Error> {
let (proto, info) = try!(hs.connect(conn)); let (proto, info) = try!(hs.connect(conn));
Ok(Peer{ Ok(Peer {
info: info, info: info,
proto: proto, proto: proto,
}) })
@ -39,7 +37,7 @@ impl Peer {
pub fn accept(conn: TcpStream, hs: &Handshake) -> Result<Peer, Error> { pub fn accept(conn: TcpStream, hs: &Handshake) -> Result<Peer, Error> {
let (proto, info) = try!(hs.handshake(conn)); let (proto, info) = try!(hs.handshake(conn));
Ok(Peer{ Ok(Peer {
info: info, info: info,
proto: proto, proto: proto,
}) })
@ -49,6 +47,14 @@ impl Peer {
self.proto.handle(na) self.proto.handle(na)
} }
pub fn send_ping(&self) -> Option<Error> {
self.proto.send_ping()
}
pub fn sent_bytes(&self) -> u64 {
self.proto.sent_bytes()
}
pub fn stop(&self) { pub fn stop(&self) {
self.proto.as_ref().close() self.proto.as_ref().close()
} }

View file

@ -13,8 +13,10 @@
// limitations under the License. // limitations under the License.
use std::cell::RefCell; use std::cell::RefCell;
use std::ops::DerefMut; use std::io::{Read, Write};
use std::ops::{Deref, DerefMut};
use std::rc::Rc; use std::rc::Rc;
use std::sync::Mutex;
use mioco; use mioco;
use mioco::sync::mpsc::{sync_channel, SyncSender}; use mioco::sync::mpsc::{sync_channel, SyncSender};
@ -25,43 +27,90 @@ use handshake::Handshake;
use msg::*; use msg::*;
use types::*; use types::*;
/// First version of our communication protocol. Manages the underlying
/// connection, listening to incoming messages and transmitting outgoing ones.
pub struct ProtocolV1 { pub struct ProtocolV1 {
// The underlying tcp connection.
conn: RefCell<TcpStream>, conn: RefCell<TcpStream>,
//msg_send: Option<SyncSender<ser::Writeable>>, // Send channel for the rest of the local system to send messages to the peer we're connected to.
msg_send: RefCell<Option<SyncSender<Vec<u8>>>>,
// Stop channel to exit the send/listen loop.
stop_send: RefCell<Option<SyncSender<u8>>>, stop_send: RefCell<Option<SyncSender<u8>>>,
// Used both to count the amount of data sent and lock writing to the conn. We can't wrap conn with
// the lock as we're always listening to receive.
sent_bytes: Mutex<u64>,
} }
impl Protocol for ProtocolV1 { impl Protocol for ProtocolV1 {
/// Main protocol connection handling loop, starts listening to incoming
/// messages and transmitting messages the rest of the local system wants
/// to send. Must be called before any interaction with a protocol instance
/// and should only be called once. Will block so also needs to be called
/// within a coroutine.
fn handle(&self, server: &NetAdapter) -> Option<ser::Error> { fn handle(&self, server: &NetAdapter) -> Option<ser::Error> {
// setup channels so we can switch between reads, writes and close // setup channels so we can switch between reads, writes and close
let (msg_send, msg_recv) = sync_channel(10); let (msg_send, msg_recv) = sync_channel(10);
let (stop_send, stop_recv) = sync_channel(1); let (stop_send, stop_recv) = sync_channel(1);
{
//self.msg_send = Some(msg_send); let mut msg_mut = self.msg_send.borrow_mut();
*msg_mut = Some(msg_send);
let mut stop_mut = self.stop_send.borrow_mut(); let mut stop_mut = self.stop_send.borrow_mut();
*stop_mut = Some(stop_send); *stop_mut = Some(stop_send);
}
let mut conn = self.conn.borrow_mut(); let mut conn = self.conn.borrow_mut();
loop { loop {
// main select loop, switches between listening, sending or stopping
select!( select!(
r:conn => { r:conn => {
// deser the header ot get the message type
let header = try_to_o!(ser::deserialize::<MsgHeader>(conn.deref_mut())); let header = try_to_o!(ser::deserialize::<MsgHeader>(conn.deref_mut()));
if !header.acceptable() { if !header.acceptable() {
continue; continue;
}
// check the message and hopefully do what's expected
match header.msg_type {
Type::Ping => {
// respond with pong
let data = try_to_o!(ser::ser_vec(&MsgHeader::new(Type::Pong)));
let mut sent_bytes = self.sent_bytes.lock().unwrap();
*sent_bytes += data.len() as u64;
try_to_o!(conn.deref_mut().write_all(&data[..]).map_err(&ser::Error::IOErr));
},
Type::Pong => {},
_ => error!("uncaught unknown"),
} }
}, },
r:msg_recv => { r:msg_recv => {
ser::serialize(conn.deref_mut(), msg_recv.recv().unwrap()); // relay a message originated from the rest of the local system
let data = &msg_recv.recv().unwrap()[..];
let mut sent_bytes = self.sent_bytes.lock().unwrap();
*sent_bytes += data.len() as u64;
try_to_o!(conn.deref_mut().write_all(data).map_err(&ser::Error::IOErr));
}, },
r:stop_recv => { r:stop_recv => {
// shuts the connection don and end the loop
stop_recv.recv(); stop_recv.recv();
conn.shutdown(Shutdown::Both); conn.shutdown(Shutdown::Both);
return None;; return None;
} }
); );
} }
} }
/// Sends a ping message to the remote peer. Will panic if handle has never
/// been called on this protocol.
fn send_ping(&self) -> Option<ser::Error> {
let data = try_to_o!(ser::ser_vec(&MsgHeader::new(Type::Ping)));
let msg_send = self.msg_send.borrow();
msg_send.as_ref().unwrap().send(data);
None
}
fn sent_bytes(&self) -> u64 {
*self.sent_bytes.lock().unwrap().deref()
}
fn close(&self) { fn close(&self) {
let stop_send = self.stop_send.borrow(); let stop_send = self.stop_send.borrow();
stop_send.as_ref().unwrap().send(0); stop_send.as_ref().unwrap().send(0);
@ -70,6 +119,11 @@ impl Protocol for ProtocolV1 {
impl ProtocolV1 { impl ProtocolV1 {
pub fn new(conn: TcpStream) -> ProtocolV1 { pub fn new(conn: TcpStream) -> ProtocolV1 {
ProtocolV1 { conn: RefCell::new(conn), /* msg_send: None, */ stop_send: RefCell::new(None) } ProtocolV1 {
conn: RefCell::new(conn),
msg_send: RefCell::new(None),
stop_send: RefCell::new(None),
sent_bytes: Mutex::new(0),
}
} }
} }

View file

@ -23,6 +23,7 @@ use std::str::FromStr;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use mioco; use mioco;
use mioco::sync::mpsc::{sync_channel, SyncSender};
use mioco::tcp::{TcpListener, TcpStream}; use mioco::tcp::{TcpListener, TcpStream};
use core::ser::Error; use core::ser::Error;
@ -37,57 +38,78 @@ fn listen_addr() -> SocketAddr {
FromStr::from_str(DEFAULT_LISTEN_ADDR).unwrap() FromStr::from_str(DEFAULT_LISTEN_ADDR).unwrap()
} }
struct DummyAdapter {} pub struct DummyAdapter {}
impl NetAdapter for DummyAdapter {} impl NetAdapter for DummyAdapter {}
/// P2P server implementation, handling bootstrapping to find and connect to /// P2P server implementation, handling bootstrapping to find and connect to
/// peers, receiving connections from other peers and keep track of all of them. /// peers, receiving connections from other peers and keep track of all of them.
pub struct Server { pub struct Server {
peers: RwLock<Vec<Arc<Peer>>>, peers: RwLock<Vec<Arc<Peer>>>,
stop_send: RefCell<Option<SyncSender<u8>>>,
} }
unsafe impl Sync for Server {}
unsafe impl Send for Server {}
// TODO TLS // TODO TLS
impl Server { impl Server {
/// Creates a new idle p2p server with no peers
pub fn new() -> Server { pub fn new() -> Server {
Server{peers: RwLock::new(Vec::new())} Server {
peers: RwLock::new(Vec::new()),
stop_send: RefCell::new(None),
} }
/// Creates a new p2p server. Opens a TCP port to allow incoming }
/// Starts the p2p server. Opens a TCP port to allow incoming
/// connections and starts the bootstrapping process to find peers. /// connections and starts the bootstrapping process to find peers.
pub fn start(& self) -> Result<(), Error> { pub fn start(&self) -> Result<(), Error> {
mioco::spawn(move || -> io::Result<()> {
let addr = DEFAULT_LISTEN_ADDR.parse().unwrap(); let addr = DEFAULT_LISTEN_ADDR.parse().unwrap();
let listener = try!(TcpListener::bind(&addr)); let listener = try!(TcpListener::bind(&addr).map_err(&Error::IOErr));
warn!("P2P server started on {}", addr); warn!("P2P server started on {}", addr);
let hs = Arc::new(Handshake::new()); let hs = Arc::new(Handshake::new());
let (stop_send, stop_recv) = sync_channel(1);
{
let mut stop_mut = self.stop_send.borrow_mut();
*stop_mut = Some(stop_send);
}
loop { loop {
let conn = try!(listener.accept()); select!(
r:listener => {
let conn = try!(listener.accept().map_err(&Error::IOErr));
let hs = hs.clone(); let hs = hs.clone();
mioco::spawn(move || -> io::Result<()> { let peer = try!(Peer::connect(conn, &hs));
let peer = try!(Peer::connect(conn, &hs).map_err(|_| io::Error::last_os_error()));
let wpeer = Arc::new(peer); let wpeer = Arc::new(peer);
// { {
// let mut peers = self.peers.write().unwrap(); let mut peers = self.peers.write().unwrap();
// peers.push(wpeer.clone()); peers.push(wpeer.clone());
// } }
mioco::spawn(move || -> io::Result<()> {
if let Some(err) = wpeer.run(&DummyAdapter{}) { if let Some(err) = wpeer.run(&DummyAdapter{}) {
error!("{:?}", err); error!("{:?}", err);
} }
Ok(()) Ok(())
}); });
},
r:stop_recv => {
stop_recv.recv();
return Ok(());
}
);
} }
Ok(())
});
Ok(())
} }
/// Stops the server. Disconnect from all peers at the same time.
pub fn stop(&self) { pub fn stop(&self) {
let peers = self.peers.write().unwrap(); let peers = self.peers.write().unwrap();
for p in peers.deref() { for p in peers.deref() {
p.stop(); p.stop();
} }
let stop_send = self.stop_send.borrow();
stop_send.as_ref().unwrap().send(0);
} }
/// Simulates an unrelated client connecting to our server. Mostly used for /// Simulates an unrelated client connecting to our server. Mostly used for

View file

@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::io::{Read, Write};
use std::net::SocketAddr; use std::net::SocketAddr;
use core::ser::Error; use core::ser::Error;
@ -36,12 +35,22 @@ pub struct PeerInfo {
} }
/// A given communication protocol agreed upon between 2 peers (usually /// A given communication protocol agreed upon between 2 peers (usually
/// ourselves and a remove) after handshake. /// ourselves and a remote) after handshake. This trait is necessary to allow
/// protocol negotiation as it gets upgraded to multiple versions.
pub trait Protocol { pub trait Protocol {
/// Starts handling protocol communication, the peer(s) is expected to be /// Starts handling protocol communication, the connection) is expected to
/// known already, usually passed during construction. /// be known already, usually passed during construction. Will typically
/// block so needs to be called withing a coroutine. Should also be called
/// only once.
fn handle(&self, na: &NetAdapter) -> Option<Error>; fn handle(&self, na: &NetAdapter) -> Option<Error>;
/// Sends a ping message to the remote peer.
fn send_ping(&self) -> Option<Error>;
/// How many bytes have been sent to the remote peer.
fn sent_bytes(&self) -> u64;
/// Close the connection to the remote peer.
fn close(&self); fn close(&self);
} }

View file

@ -17,7 +17,7 @@ extern crate mioco;
extern crate env_logger; extern crate env_logger;
use std::io; use std::io;
use std::thread; use std::sync::Arc;
use std::time; use std::time;
#[test] #[test]
@ -25,16 +25,30 @@ fn peer_handshake() {
env_logger::init().unwrap(); env_logger::init().unwrap();
mioco::start(|| -> io::Result<()> { mioco::start(|| -> io::Result<()> {
let server = p2p::Server::new(); let server = Arc::new(p2p::Server::new());
server.start(); let in_server = server.clone();
mioco::spawn(move || -> io::Result<()> {
try!(in_server.start());
Ok(())
});
// given server a little time to start // given server a little time to start
mioco::sleep(time::Duration::from_millis(200)); mioco::sleep(time::Duration::from_millis(200));
let addr = p2p::DEFAULT_LISTEN_ADDR.parse().unwrap(); let addr = p2p::DEFAULT_LISTEN_ADDR.parse().unwrap();
try!(p2p::Server::connect_as_client(addr).map_err(|_| io::Error::last_os_error())); let peer = try!(p2p::Server::connect_as_client(addr).map_err(|_| io::Error::last_os_error()));
let peer = Arc::new(peer);
let in_peer = peer.clone();
mioco::spawn(move || -> io::Result<()> {
in_peer.run(&p2p::DummyAdapter{});
Ok(())
});
mioco::sleep(time::Duration::from_millis(100));
peer.send_ping();
mioco::sleep(time::Duration::from_millis(100));
assert!(peer.sent_bytes() > 0);
server.stop(); server.stop();
mioco::shutdown(); Ok(())
}).unwrap().unwrap(); }).unwrap().unwrap();
} }