commit 84813fa011ef987097672d958c6118daf3c63653 Author: Dennis Schwerdel Date: Thu Nov 19 16:34:20 2015 +0100 Initial working version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2bb746f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target +Cargo.lock +._* diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1548432 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "ethcloud" +version = "0.1.0" +authors = ["Dennis Schwerdel "] +build = "build.rs" + +[dependencies] +time = "0.1" +docopt = "0.6" +rustc-serialize = "0.3" +log = "*" +env_logger = "*" + +[build-dependencies] +gcc = "0.3" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..b9d7f44 --- /dev/null +++ b/build.rs @@ -0,0 +1,5 @@ +extern crate gcc; + +fn main() { + gcc::Config::new().file("src/c/tapdev.c").include("src").compile("libtapdev.a"); +} \ No newline at end of file diff --git a/src/c/Makefile b/src/c/Makefile new file mode 100644 index 0000000..6eed684 --- /dev/null +++ b/src/c/Makefile @@ -0,0 +1,9 @@ +default: libtapdev.a + +tapdev.o: tapdev.c + gcc -Os -c tapdev.c + +libtapdev.a: tapdev.o + ar rcs libtapdev.a tapdev.o + + diff --git a/src/c/tapdev.c b/src/c/tapdev.c new file mode 100644 index 0000000..53698a3 --- /dev/null +++ b/src/c/tapdev.c @@ -0,0 +1,15 @@ +#include +#include +#include +#include +#include + +int32_t setup_tap_device(int32_t fd, char *ifname) { + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TAP | IFF_NO_PI; + strncpy(ifr.ifr_name, ifname, IFNAMSIZ); + if (ioctl(fd, TUNSETIFF, (void *)&ifr) < 0) return 1; + strncpy(ifname, ifr.ifr_name, IFNAMSIZ); + return 0; +} \ No newline at end of file diff --git a/src/ethcloud.rs b/src/ethcloud.rs new file mode 100644 index 0000000..4f8ca4a --- /dev/null +++ b/src/ethcloud.rs @@ -0,0 +1,301 @@ +use std::net::{SocketAddr, ToSocketAddrs}; +use std::collections::HashMap; +use std::hash::Hasher; +use std::net::UdpSocket; +use std::io::{Read, ErrorKind}; +use std::os::unix::io::AsRawFd; +use std::fmt; + +use time::{Duration, SteadyTime}; +use libc; + +pub use ethernet::{encode as eth_encode, decode as eth_decode, Frame as EthernetFrame}; +pub use tapdev::TapDevice; +pub use udpmessage::{encode as udp_encode, decode as udp_decode, Message as UdpMessage}; + + +#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct Mac(pub [u8; 6]); + +impl fmt::Debug for Mac { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(formatter, "{:x}:{:x}:{:x}:{:x}:{:x}:{:x}", + self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]) + } +} + + +pub type Token = u64; + +#[derive(Debug)] +pub enum Error { + ParseError(&'static str), + WrongToken(Token), + SocketError(&'static str), + TapdevError(&'static str), +} + + +struct PeerList { + timeout: Duration, + peers: HashMap +} + +impl PeerList { + fn new(timeout: Duration) -> PeerList { + PeerList{peers: HashMap::new(), timeout: timeout} + } + + fn timeout(&mut self) { + let now = SteadyTime::now(); + let mut del: Vec = Vec::new(); + for (&addr, &timeout) in &self.peers { + if timeout < now { + del.push(addr); + } + } + for addr in del { + debug!("Forgot peer: {:?}", addr); + self.peers.remove(&addr); + } + } + + fn contains(&mut self, addr: &SocketAddr) -> bool { + self.peers.contains_key(addr) + } + + fn add(&mut self, addr: &SocketAddr) { + if self.peers.insert(*addr, SteadyTime::now()+self.timeout).is_none() { + info!("New peer: {:?}", addr); + } + } + + fn as_vec(&self) -> Vec { + self.peers.keys().map(|addr| *addr).collect() + } + + fn remove(&mut self, addr: &SocketAddr) { + if self.peers.remove(&addr).is_some() { + info!("Removed peer: {:?}", addr); + } + } +} + + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +struct MacTableKey { + mac: Mac, + vlan: u16 +} + +struct MacTableValue { + address: SocketAddr, + timeout: SteadyTime +} + +struct MacTable { + table: HashMap, + timeout: Duration +} + +impl MacTable { + fn new(timeout: Duration) -> MacTable { + MacTable{table: HashMap::new(), timeout: timeout} + } + + fn timeout(&mut self) { + let now = SteadyTime::now(); + let mut del: Vec = Vec::new(); + for (&key, val) in &self.table { + if val.timeout < now { + del.push(key); + } + } + for key in del { + info!("Forgot mac: {:?} (vlan {})", key.mac, key.vlan); + self.table.remove(&key); + } + } + + fn learn(&mut self, mac: &Mac, vlan: u16, addr: &SocketAddr) { + let key = MacTableKey{mac: *mac, vlan: vlan}; + let value = MacTableValue{address: *addr, timeout: SteadyTime::now()+self.timeout}; + if self.table.insert(key, value).is_none() { + info!("Learned mac: {:?} (vlan {}) => {}", mac, vlan, addr); + } + } + + fn lookup(&self, mac: &Mac, vlan: u16) -> Option { + let key = MacTableKey{mac: *mac, vlan: vlan}; + match self.table.get(&key) { + Some(value) => Some(value.address), + None => None + } + } +} + + +pub struct EthCloud { + peers: PeerList, + mactable: MacTable, + socket: UdpSocket, + tapdev: TapDevice, + token: Token, + next_peerlist: SteadyTime, + update_freq: Duration +} + +impl EthCloud { + pub fn new(device: &str, listen: String, token: Token, mac_timeout: Duration, peer_timeout: Duration) -> Self { + let socket = match UdpSocket::bind(&listen as &str) { + Ok(socket) => socket, + _ => panic!("Failed to open socket") + }; + let res: i32; + unsafe { + res = libc::fcntl(socket.as_raw_fd(), libc::consts::os::posix01::F_SETFL, libc::consts::os::extra::O_NONBLOCK); + } + if res != 0 { + panic!("Failed to set socket to non-blocking"); + } + let tapdev = match TapDevice::new(device) { + Ok(tapdev) => tapdev, + _ => panic!("Failed to open tap device") + }; + info!("Opened tap device {}", tapdev.ifname()); + EthCloud{ + peers: PeerList::new(peer_timeout), + mactable: MacTable::new(mac_timeout), + socket: socket, + tapdev: tapdev, + token: token, + next_peerlist: SteadyTime::now(), + update_freq: peer_timeout/2 + } + } + + fn send_msg(&mut self, addr: A, msg: &UdpMessage) -> Result<(), Error> { + debug!("Sending {:?} to {}", msg, addr); + let mut buffer = [0u8; 64*1024]; + let size = udp_encode(self.token, msg, &mut buffer); + match self.socket.send_to(&buffer[..size], addr) { + Ok(written) if written == size => Ok(()), + Ok(_) => Err(Error::SocketError("Sent out truncated packet")), + Err(e) => { + error!("Failed to send via network {:?}", e); + Err(Error::SocketError("IOError when sending")) + } + } + } + + pub fn connect(&mut self, addr: A) -> Result<(), Error> { + info!("Connecting to {}", addr); + self.send_msg(addr, &UdpMessage::GetPeers) + } + + fn housekeep(&mut self) -> Result<(), Error> { + self.peers.timeout(); + self.mactable.timeout(); + if self.next_peerlist <= SteadyTime::now() { + debug!("Send peer list to all peers"); + let peers = self.peers.as_vec(); + let msg = UdpMessage::Peers(peers); + for addr in &self.peers.as_vec() { + try!(self.send_msg(addr, &msg)); + } + self.next_peerlist = SteadyTime::now() + self.update_freq; + } + Ok(()) + } + + fn handle_ethernet_frame(&mut self, frame: EthernetFrame) -> Result<(), Error> { + debug!("Read ethernet frame from tap {:?}", frame); + match self.mactable.lookup(frame.dst, frame.vlan) { + Some(addr) => { + debug!("Found destination for {:?} (vlan {}) => {}", frame.dst, frame.vlan, addr); + try!(self.send_msg(addr, &UdpMessage::Frame(frame))) + }, + None => { + debug!("No destination for {:?} (vlan {}) found, broadcasting", frame.dst, frame.vlan); + let msg = UdpMessage::Frame(frame); + for addr in &self.peers.as_vec() { + try!(self.send_msg(addr, &msg)); + } + } + } + Ok(()) + } + + fn handle_net_message(&mut self, peer: SocketAddr, token: Token, msg: UdpMessage) -> Result<(), Error> { + if token != self.token { + info!("Ignoring message from {} with wrong token {}", peer, token); + return Err(Error::WrongToken(token)); + } + debug!("Recieved {:?} from {}", msg, peer); + match msg { + UdpMessage::Frame(frame) => { + self.peers.add(&peer); + self.mactable.learn(frame.src, frame.vlan, &peer); + let mut buffer = [0u8; 64*1024]; + let size = eth_encode(&frame, &mut buffer); + debug!("Writing ethernet frame to tap: {:?}", frame); + match self.tapdev.write(&buffer[..size]) { + Ok(()) => (), + Err(e) => { + error!("Failed to send via tap device {:?}", e); + return Err(Error::TapdevError("Failed to write to tap device")); + } + } + }, + UdpMessage::Peers(peers) => { + self.peers.add(&peer); + for p in &peers { + if ! self.peers.contains(p) { + try!(self.connect(p)); + } + } + }, + UdpMessage::GetPeers => { + self.peers.add(&peer); + let peers = self.peers.as_vec(); + try!(self.send_msg(peer, &UdpMessage::Peers(peers))); + }, + UdpMessage::Close => self.peers.remove(&peer) + } + Ok(()) + } + + pub fn run(&mut self) { + let mut buffer = [0u8; 64*1024]; + loop { + match self.socket.recv_from(&mut buffer) { + Ok((size, src)) => { + match udp_decode(&buffer[..size]).and_then(|(token, msg)| self.handle_net_message(src, token, msg)) { + Ok(_) => (), + Err(e) => error!("Error: {:?}", e) + } + }, + Err(error) => match error.kind() { + ErrorKind::WouldBlock => (), + _ => panic!("Failed to read from network socket") + } + } + match self.tapdev.read(&mut buffer) { + Ok(size) => { + match eth_decode(&buffer[..size]).and_then(|frame| self.handle_ethernet_frame(frame)) { + Ok(_) => (), + Err(e) => error!("Error: {:?}", e) + } + }, + Err(error) => match error.kind() { + ErrorKind::WouldBlock => (), + _ => panic!("Failed to read from tap device") + } + } + match self.housekeep() { + Ok(_) => (), + Err(e) => error!("Error: {:?}", e) + } + } + } +} diff --git a/src/ethernet.rs b/src/ethernet.rs new file mode 100644 index 0000000..d7a6c98 --- /dev/null +++ b/src/ethernet.rs @@ -0,0 +1,102 @@ +use std::{mem, slice, ptr, fmt}; + +pub use super::{Mac, Error}; + +unsafe fn as_bytes(obj: &T) -> &[u8] { + slice::from_raw_parts(mem::transmute::<&T, *const u8>(obj), mem::size_of::()) +} + +unsafe fn as_obj(data: &[u8]) -> &T { + assert!(data.len() >= mem::size_of::()); + mem::transmute(data.as_ptr()) +} + +#[derive(PartialEq)] +pub struct Frame<'a> { + pub vlan: u16, + pub src: &'a Mac, + pub dst: &'a Mac, + pub payload: &'a [u8] +} + +impl<'a> fmt::Debug for Frame<'a> { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(formatter, "src: {:?}, dst: {:?}, vlan: {}, payload: {} bytes", + self.src, self.dst, self.vlan, self.payload.len()) + } +} + +pub fn decode(data: &[u8]) -> Result { + if data.len() < 14 { + return Err(Error::ParseError("Frame is too short")); + } + let mut pos = 0; + let dst = unsafe { as_obj::(&data[pos..]) }; + pos += mem::size_of::(); + let src = unsafe { as_obj::(&data[pos..]) }; + pos += mem::size_of::(); + let mut vlan = 0; + let mut payload = &data[pos..]; + if data[pos] == 0x81 && data[pos+1] == 0x00 { + pos += 2; + if data.len() < pos + 2 { + return Err(Error::ParseError("Vlan frame is too short")); + } + vlan = u16::from_be(* unsafe { as_obj::(&data[pos..]) }); + pos += 2; + payload = &data[pos..]; + } + Ok(Frame{vlan: vlan, src: src, dst: dst, payload: payload}) +} + +pub fn encode(frame: &Frame, buf: &mut [u8]) -> usize { + assert!(buf.len() >= 16 + frame.payload.len()); + let mut pos = 0; + unsafe { + let dst_dat = as_bytes::(frame.dst); + ptr::copy_nonoverlapping(dst_dat.as_ptr(), buf[pos..].as_mut_ptr(), dst_dat.len()); + pos += dst_dat.len(); + let src_dat = as_bytes::(frame.src); + ptr::copy_nonoverlapping(src_dat.as_ptr(), buf[pos..].as_mut_ptr(), src_dat.len()); + pos += src_dat.len(); + if frame.vlan != 0 { + buf[pos] = 0x81; buf[pos+1] = 0x00; + pos += 2; + let vlan_dat = mem::transmute::(frame.vlan.to_be()); + ptr::copy_nonoverlapping(vlan_dat.as_ptr(), buf[pos..].as_mut_ptr(), vlan_dat.len()); + pos += vlan_dat.len(); + } + ptr::copy_nonoverlapping(frame.payload.as_ptr(), buf[pos..].as_mut_ptr(), frame.payload.len()); + } + pos += frame.payload.len(); + pos +} + + +#[test] +fn without_vlan() { + let src = Mac([1,2,3,4,5,6]); + let dst = Mac([6,5,4,3,2,1]); + let payload = [1,2,3,4,5,6,7,8]; + let mut buf = [0u8; 1024]; + let frame = Frame{src: &src, dst: &dst, vlan: 0, payload: &payload}; + let size = encode(&frame, &mut buf); + assert_eq!(size, 20); + assert_eq!(&buf[..size], &[6,5,4,3,2,1,1,2,3,4,5,6,1,2,3,4,5,6,7,8]); + let frame2 = decode(&buf[..size]).unwrap(); + assert_eq!(frame, frame2); +} + +#[test] +fn with_vlan() { + let src = Mac([1,2,3,4,5,6]); + let dst = Mac([6,5,4,3,2,1]); + let payload = [1,2,3,4,5,6,7,8]; + let mut buf = [0u8; 1024]; + let frame = Frame{src: &src, dst: &dst, vlan: 1234, payload: &payload}; + let size = encode(&frame, &mut buf); + assert_eq!(size, 24); + assert_eq!(&buf[..size], &[6,5,4,3,2,1,1,2,3,4,5,6,0x81,0,4,210,1,2,3,4,5,6,7,8]); + let frame2 = decode(&buf[..size]).unwrap(); + assert_eq!(frame, frame2); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0985a7a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,86 @@ +#![feature(libc)] + +#[macro_use] extern crate log; +extern crate time; +extern crate libc; +extern crate docopt; +extern crate rustc_serialize; + +mod udpmessage; +mod tapdev; +mod ethernet; +mod ethcloud; + +use time::Duration; +use docopt::Docopt; + +pub use ethcloud::{Error, Mac, Token, EthCloud}; +pub use ethernet::{encode as eth_encode, decode as eth_decode, Frame as EthernetFrame}; +pub use tapdev::TapDevice; +pub use udpmessage::{encode as udp_encode, decode as udp_decode, Message as UdpMessage}; + + + +struct SimpleLogger; + +impl log::Log for SimpleLogger { + fn enabled(&self, _metadata: &log::LogMetadata) -> bool { + true + } + + fn log(&self, record: &log::LogRecord) { + if self.enabled(record.metadata()) { + println!("{} - {}", record.level(), record.args()); + } + } +} + + +static USAGE: &'static str = " +Usage: + ethcloud [options] + +Options: + -d , --device Name of the tap device [default: ethcloud%d] + -l , --listen Address to listen on [default: 0.0.0.0:3210] + -t , --token Token that identifies the network [default: 0] + -c , --connect List of peers (addr:port) to connect to + --peer-timeout Peer timeout in seconds [default: 300] + --mac-timeout Mac table entry timeout in seconds [default: 60] + -v, --verbose Log verbosely +"; + +#[derive(RustcDecodable, Debug)] +struct Args { + flag_device: String, + flag_listen: String, + flag_token: Token, + flag_connect: Vec, + flag_peer_timeout: usize, + flag_mac_timeout: usize, + flag_verbose: bool +} + +fn main() { + let args: Args = Docopt::new(USAGE).and_then(|d| d.decode()).unwrap_or_else(|e| e.exit()); + log::set_logger(|max_log_level| { + if args.flag_verbose { + max_log_level.set(log::LogLevelFilter::Debug); + } else { + max_log_level.set(log::LogLevelFilter::Info); + } + Box::new(SimpleLogger) + }).unwrap(); + debug!("Args: {:?}", args); + let mut tapcloud = EthCloud::new( + &args.flag_device, + args.flag_listen, + args.flag_token, + Duration::seconds(args.flag_mac_timeout as i64), + Duration::seconds(args.flag_peer_timeout as i64) + ); + for addr in args.flag_connect { + tapcloud.connect(&addr as &str).expect("Failed to send"); + } + tapcloud.run(); +} diff --git a/src/tapdev.rs b/src/tapdev.rs new file mode 100644 index 0000000..71b2aa8 --- /dev/null +++ b/src/tapdev.rs @@ -0,0 +1,49 @@ +use std::fs; +use std::io::{Read, Write, Result as IoResult, Error as IoError}; +use std::os::unix::io::AsRawFd; + +use libc; + +extern { + fn setup_tap_device(fd: i32, ifname: *mut u8) -> i32; +} + +pub struct TapDevice { + fd: fs::File, + ifname: String +} + +impl TapDevice { + pub fn new(ifname: &str) -> IoResult { + let fd = try!(fs::OpenOptions::new().read(true).write(true).open("/dev/net/tun")); + let mut ifname_string = String::with_capacity(32); + ifname_string.push_str(ifname); + ifname_string.push('\0'); + let mut ifname_c = ifname_string.into_bytes(); + let mut res: i32; + unsafe { + res = setup_tap_device(fd.as_raw_fd(), ifname_c.as_mut_ptr()); + if res == 0 { + res = libc::fcntl(fd.as_raw_fd(), libc::consts::os::posix01::F_SETFL, libc::consts::os::extra::O_NONBLOCK); + } + } + match res { + 0 => Ok(TapDevice{fd: fd, ifname: String::from_utf8(ifname_c).unwrap()}), + _ => Err(IoError::last_os_error()) + } + } + + pub fn ifname(&self) -> &str { + &self.ifname + } + + #[inline(always)] + pub fn read(&mut self, buffer: &mut [u8]) -> IoResult { + self.fd.read(buffer) + } + + #[inline(always)] + pub fn write(&mut self, buffer: &[u8]) -> IoResult<()> { + self.fd.write_all(buffer) + } +} diff --git a/src/udpmessage.rs b/src/udpmessage.rs new file mode 100644 index 0000000..d4a8191 --- /dev/null +++ b/src/udpmessage.rs @@ -0,0 +1,191 @@ +use std::{mem, ptr, fmt}; +use std::net::{SocketAddr, SocketAddrV4, Ipv4Addr}; +use std::u16; + +pub use super::{Mac, Error, Token, EthernetFrame, eth_decode, eth_encode}; + +unsafe fn as_obj(data: &[u8]) -> &T { + assert!(data.len() >= mem::size_of::()); + mem::transmute(data.as_ptr()) +} + +#[derive(PartialEq)] +pub enum Message<'a> { + Frame(EthernetFrame<'a>), + Peers(Vec), + GetPeers, + Close, +} + +impl<'a> fmt::Debug for Message<'a> { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + &Message::Frame(ref frame) => write!(formatter, "Frame({:?})", frame), + &Message::Peers(ref peers) => { + try!(write!(formatter, "Peers [")); + let mut first = true; + for p in peers { + if !first { + try!(write!(formatter, ", ")); + } + first = false; + try!(p.fmt(formatter)); + } + write!(formatter, "]") + }, + &Message::GetPeers => write!(formatter, "GetPeers"), + &Message::Close => write!(formatter, "Close"), + } + } +} + +pub fn decode(data: &[u8]) -> Result<(Token, Message), Error> { + if data.len() < 1 + mem::size_of::() { + return Err(Error::ParseError("Empty message")); + } + let mut pos = 0; + let token = Token::from_be(* unsafe { as_obj::(&data[pos..]) }); + pos += mem::size_of::(); + match data[pos] { + 0 => { + pos += 1; + Ok((token, Message::Frame(try!(eth_decode(&data[pos..]))))) + }, + 1 => { + pos += 1; + if data.len() < pos + 1 { + return Err(Error::ParseError("Empty peers")); + } + let count = data[pos]; + pos += 1; + let len = count as usize * 6; + if data.len() < pos + len { + return Err(Error::ParseError("Peer data too short")); + } + let mut peers = Vec::with_capacity(count as usize); + for _ in 0..count { + let (ip, port) = unsafe { + let ip = as_obj::<[u8; 4]>(&data[pos..]); + pos += 4; + let port = *as_obj::(&data[pos..]); + let port = u16::from_be(port); + pos += 2; + (ip, port) + }; + let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(ip[0], ip[1], ip[2], ip[3]), port)); + peers.push(addr); + } + Ok((token, Message::Peers(peers))) + }, + 2 => Ok((token, Message::GetPeers)), + 3 => Ok((token, Message::Close)), + _ => Err(Error::ParseError("Unknown message type")) + } +} + +pub fn encode(token: Token, msg: &Message, buf: &mut [u8]) -> usize { + assert!(buf.len() >= mem::size_of::() + 1); + let mut pos = 0; + let token_dat = unsafe { mem::transmute::(token.to_be()) }; + unsafe { ptr::copy_nonoverlapping(token_dat.as_ptr(), buf[pos..].as_mut_ptr(), token_dat.len()) }; + pos += token_dat.len(); + match msg { + &Message::Frame(ref frame) => { + buf[pos] = 0; + pos += 1; + pos += eth_encode(&frame, &mut buf[pos..]) + }, + &Message::Peers(ref peers) => { + buf[pos] = 1; + pos += 1; + let count_pos = pos; + pos += 1; + assert!(buf.len() >= 2 + peers.len() * mem::size_of::()); + let mut count = 0; + for p in peers { + match p { + &SocketAddr::V4(addr) => { + let ip = addr.ip().octets(); + let port = addr.port(); + unsafe { + ptr::copy_nonoverlapping(ip.as_ptr(), buf[pos..].as_mut_ptr(), ip.len()); + pos += ip.len(); + let port = mem::transmute::(port.to_be()); + ptr::copy_nonoverlapping(port.as_ptr(), buf[pos..].as_mut_ptr(), port.len()); + pos += port.len(); + } + count += 1; + }, + &SocketAddr::V6(_addr) => unimplemented!() + } + }; + buf[count_pos] = count; + }, + &Message::GetPeers => { + buf[pos] = 2; + pos += 1; + }, + &Message::Close => { + buf[pos] = 3; + pos += 1; + } + } + pos +} + + +#[test] +fn encode_message_packet() { + let token = 134; + let src = Mac([1,2,3,4,5,6]); + let dst = Mac([7,8,9,10,11,12]); + let payload = [1,2,3,4,5]; + let msg = Message::Frame(EthernetFrame{src: &src, dst: &dst, vlan: 0, payload: &payload}); + let mut buf = [0; 1024]; + let size = encode(token, &msg, &mut buf[..]); + assert_eq!(size, 26); + assert_eq!(&buf[..9], &[0,0,0,0,0,0,0,134,0]); + let (token2, msg2) = decode(&buf[..size]).unwrap(); + assert_eq!(token, token2); + assert_eq!(msg, msg2); +} + +#[test] +fn encode_message_peers() { + use std::str::FromStr; + let token = 134; + let msg = Message::Peers(vec![SocketAddr::from_str("1.2.3.4:123").unwrap(), SocketAddr::from_str("5.6.7.8:12345").unwrap()]); + let mut buf = [0; 1024]; + let size = encode(token, &msg, &mut buf[..]); + assert_eq!(size, 22); + assert_eq!(&buf[..size], &[0,0,0,0,0,0,0,134,1,2,1,2,3,4,0,123,5,6,7,8,48,57]); + let (token2, msg2) = decode(&buf[..size]).unwrap(); + assert_eq!(token, token2); + assert_eq!(msg, msg2); +} + +#[test] +fn encode_message_getpeers() { + let token = 134; + let msg = Message::GetPeers; + let mut buf = [0; 1024]; + let size = encode(token, &msg, &mut buf[..]); + assert_eq!(size, 9); + assert_eq!(&buf[..size], &[0,0,0,0,0,0,0,134,2]); + let (token2, msg2) = decode(&buf[..size]).unwrap(); + assert_eq!(token, token2); + assert_eq!(msg, msg2); +} + +#[test] +fn encode_message_close() { + let token = 134; + let msg = Message::Close; + let mut buf = [0; 1024]; + let size = encode(token, &msg, &mut buf[..]); + assert_eq!(size, 9); + assert_eq!(&buf[..size], &[0,0,0,0,0,0,0,134,3]); + let (token2, msg2) = decode(&buf[..size]).unwrap(); + assert_eq!(token, token2); + assert_eq!(msg, msg2); +}