From 95234df31f1b9b4e397ac4a0c84ea468e2a3ad66 Mon Sep 17 00:00:00 2001 From: Aaron Weiss Date: Sat, 8 Nov 2014 23:07:57 -0500 Subject: [PATCH] Refactored library to be Stream-based, added a wrapper to create streams out of Buffers and Writers, and completed SSL support. --- .travis.yml | 3 ++ src/conn.rs | 127 +++++++++++++++++++++++++++++--------------- src/data/mod.rs | 4 ++ src/server/mod.rs | 50 ++++++++--------- src/server/utils.rs | 48 ++++++++--------- 5 files changed, 140 insertions(+), 92 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5dd5a4a..6e357f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,11 @@ script: - chmod +x mktestconfig.sh - ./mktestconfig.sh - cargo build --verbose + - cargo build --verbose --features ssl - cargo test --verbose + - cargo test --verbose --features ssl - cargo doc --verbose + - cargo doc --verbose --features ssl notifications: irc: channels: diff --git a/src/conn.rs b/src/conn.rs index a02ea17..6c82c58 100644 --- a/src/conn.rs +++ b/src/conn.rs @@ -1,51 +1,60 @@ //! Thread-safe connections on any IrcWriters and IrcReaders. #![experimental] use std::sync::{Mutex, MutexGuard}; -use std::io::{BufferedReader, BufferedWriter, IoResult, TcpStream}; +use std::io::{BufferedStream, IoResult, MemWriter, TcpStream}; #[cfg(feature = "ssl")] use std::io::{IoError, OtherIoError}; -use data::kinds::{IrcWriter, IrcReader}; +use data::kinds::{IrcReader, IrcStream, IrcWriter}; use data::message::Message; #[cfg(feature = "ssl")] use openssl::ssl::{SslContext, SslStream, Tlsv1}; #[cfg(feature = "ssl")] use openssl::ssl::error::SslError; /// A thread-safe connection. #[experimental] -pub struct Connection where T: IrcWriter, U: IrcReader { - writer: Mutex, - reader: Mutex, +pub struct Connection where T: IrcStream { + stream: Mutex } -impl Connection, BufferedReader> { +impl Connection> { /// Creates a thread-safe TCP connection to the specified server. #[experimental] - pub fn connect(host: &str, port: u16) -> IoResult, BufferedReader>> { + pub fn connect(host: &str, port: u16) -> IoResult>> { let socket = try!(TcpStream::connect(format!("{}:{}", host, port)[])); - Ok(Connection::new(BufferedWriter::new(UnsecuredTcpStream(socket.clone())), - BufferedReader::new(UnsecuredTcpStream(socket)))) + Ok(Connection::new(BufferedStream::new(UnsecuredTcpStream(socket)))) } /// Creates a thread-safe TCP connection to the specified server over SSL. /// If the library is compiled without SSL support, this method panics. #[experimental] #[cfg(feature = "ssl")] - pub fn connect_ssl(host: &str, port: u16) -> IoResult, BufferedReader>> { + pub fn connect_ssl(host: &str, port: u16) -> IoResult>> { let socket = try!(TcpStream::connect(format!("{}:{}", host, port)[])); let ssl = try!(ssl_to_io(SslContext::new(Tlsv1))); - let input = try!(ssl_to_io(SslStream::new(&ssl, socket.clone()))); - let output = try!(ssl_to_io(SslStream::new(&ssl, socket))); - Ok(Connection::new(BufferedWriter::new(SslTcpStream(input)), - BufferedReader::new(SslTcpStream(output)))) + let ssl_socket = try!(ssl_to_io(SslStream::new(&ssl, socket))); + Ok(Connection::new(BufferedStream::new(SslTcpStream(ssl_socket)))) } /// Creates a thread-safe TCP connection to the specified server over SSL. /// If the library is compiled without SSL support, this method panics. #[experimental] #[cfg(not(feature = "ssl"))] - pub fn connect_ssl(host: &str, port: u16) -> IoResult, BufferedReader>> { + pub fn connect_ssl(host: &str, port: u16) -> IoResult>> { panic!("Cannot connect to {}:{} over SSL without compiling with SSL support.", host, port) } } +/// Converts a Result into an IoResult. +#[cfg(feature = "ssl")] +fn ssl_to_io(res: Result) -> IoResult { + match res { + Ok(x) => Ok(x), + Err(e) => Err(IoError { + kind: OtherIoError, + desc: "An SSL error occurred.", + detail: Some(format!("{}", e)), + }), + } +} + /// An abstraction over different networked streams. #[experimental] pub enum NetStream { @@ -77,67 +86,99 @@ impl Writer for NetStream { } } -#[cfg(feature = "ssl")] -fn ssl_to_io(res: Result) -> IoResult { - match res { - Ok(x) => Ok(x), - Err(e) => Err(IoError { - kind: OtherIoError, - desc: "An SSL error occurred.", - detail: Some(format!("{}", e)), - }), - } -} - -impl Connection where T: IrcWriter, U: IrcReader { - /// Creates a new connection from any arbitrary IrcWriter and IrcReader. +impl Connection { + /// Creates a new connection from any arbitrary IrcStream. #[experimental] - pub fn new(writer: T, reader: U) -> Connection { + pub fn new(stream: T) -> Connection { Connection { - writer: Mutex::new(writer), - reader: Mutex::new(reader), + stream: Mutex::new(stream), } } /// Sends a Message over this connection. #[experimental] pub fn send(&self, message: Message) -> IoResult<()> { - let mut send = self.writer.lock(); - try!(send.write_str(message.into_string()[])); - send.flush() + let mut stream = self.stream.lock(); + try!(stream.write_str(message.into_string()[])); + stream.flush() } /// Receives a single line from this connection. #[experimental] pub fn recv(&self) -> IoResult { - self.reader.lock().read_line() + self.stream.lock().read_line() } - /// Acquires the Writer lock. + /// Acquires the Stream lock. #[experimental] - pub fn writer<'a>(&'a self) -> MutexGuard<'a, T> { - self.writer.lock() + pub fn stream<'a>(&'a self) -> MutexGuard<'a, T> { + self.stream.lock() + } +} + +/// An IrcStream built from an IrcWriter and an IrcReader. +#[experimental] +pub struct IoStream { + writer: T, + reader: U, +} + +impl IoStream { + /// Creates a new IoStream from the given IrcWriter and IrcReader. + #[experimental] + pub fn new(writer: T, reader: U) -> IoStream { + IoStream { writer: writer, reader: reader } + } +} + +impl IoStream { + pub fn value(&self) -> Vec { + self.writer.get_ref().to_vec() + } +} + +impl Buffer for IoStream { + fn fill_buf<'a>(&'a mut self) -> IoResult<&'a [u8]> { + self.reader.fill_buf() + } + + fn consume(&mut self, amt: uint) { + self.reader.consume(amt) + } +} + +impl Reader for IoStream { + fn read(&mut self, buf: &mut [u8]) -> IoResult { + self.reader.read(buf) + } +} + +impl Writer for IoStream { + fn write(&mut self, buf: &[u8]) -> IoResult<()> { + self.writer.write(buf) } } #[cfg(test)] mod test { - use super::Connection; + use super::{Connection, IoStream}; use std::io::{MemReader, MemWriter}; use std::io::util::{NullReader, NullWriter}; use data::message::Message; #[test] fn send() { - let conn = Connection::new(MemWriter::new(), NullReader); + let conn = Connection::new(IoStream::new(MemWriter::new(), NullReader)); assert!(conn.send(Message::new(None, "PRIVMSG", Some(vec!["test"]), Some("Testing!"))).is_ok()); - let data = String::from_utf8(conn.writer().get_ref().to_vec()).unwrap(); + let data = String::from_utf8(conn.stream().value()).unwrap(); assert_eq!(data[], "PRIVMSG test :Testing!\r\n"); } #[test] fn recv() { - let conn = Connection::new(NullWriter, MemReader::new("PRIVMSG test :Testing!\r\n".as_bytes().to_vec())); + let conn = Connection::new(IoStream::new( + NullWriter, MemReader::new("PRIVMSG test :Testing!\r\n".as_bytes().to_vec()) + )); assert_eq!(conn.recv().unwrap()[], "PRIVMSG test :Testing!\r\n"); } } diff --git a/src/data/mod.rs b/src/data/mod.rs index bc4a272..b1e14fd 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -17,6 +17,10 @@ pub mod kinds { #[unstable] pub trait IrcReader: Buffer + Sized + Send + 'static {} impl IrcReader for T where T: Buffer + Sized + Send + 'static {} + /// Trait describing all possible Streams for this library. + #[unstable] + pub trait IrcStream: IrcWriter + IrcReader {} + impl IrcStream for T where T: IrcWriter + IrcReader {} } pub mod command; diff --git a/src/server/mod.rs b/src/server/mod.rs index 769d768..16e56c9 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,38 +1,38 @@ //! Interface for working with IRC Servers #![experimental] -use std::io::{BufferedReader, BufferedWriter, IoResult}; +use std::io::{BufferedStream, IoResult}; use conn::{Connection, NetStream}; use data::command::{Command, JOIN, PONG}; use data::config::Config; -use data::kinds::{IrcReader, IrcWriter}; +use data::kinds::IrcStream; use data::message::Message; pub mod utils; /// Trait describing core Server functionality. #[experimental] -pub trait Server<'a, T, U> { +pub trait Server<'a, T> { /// Gets the configuration being used with this Server. fn config(&self) -> &Config; /// Sends a Command to this Server. fn send(&self, _: Command) -> IoResult<()>; /// Gets an Iterator over Messages received by this Server. - fn iter(&'a self) -> ServerIterator<'a, T, U>; + fn iter(&'a self) -> ServerIterator<'a, T>; } /// A thread-safe implementation of an IRC Server connection. #[experimental] -pub struct IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader { +pub struct IrcServer<'a, T> where T: IrcStream { /// The thread-safe IRC connection. - conn: Connection, + conn: Connection, /// The configuration used with this connection. config: Config } -impl<'a> IrcServer<'a, BufferedWriter, BufferedReader> { +impl<'a> IrcServer<'a, BufferedStream> { /// Creates a new IRC Server connection from the configuration at the specified path, connecting immediately. #[experimental] - pub fn new(config: &str) -> IoResult, BufferedReader>> { + pub fn new(config: &str) -> IoResult>> { let config = try!(Config::load_utf8(config)); let conn = try!(if config.use_ssl { Connection::connect_ssl(config.server[], config.port) @@ -44,7 +44,7 @@ impl<'a> IrcServer<'a, BufferedWriter, BufferedReader> { /// Creates a new IRC server connection from the specified configuration, connecting immediately. #[experimental] - pub fn from_config(config: Config) -> IoResult, BufferedReader>> { + pub fn from_config(config: Config) -> IoResult>> { let conn = try!(if config.use_ssl { Connection::connect_ssl(config.server[], config.port) } else { @@ -54,7 +54,7 @@ impl<'a> IrcServer<'a, BufferedWriter, BufferedReader> { } } -impl<'a, T, U> Server<'a, T, U> for IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader { +impl<'a, T> Server<'a, T> for IrcServer<'a, T> where T: IrcStream { fn config(&self) -> &Config { &self.config } @@ -63,15 +63,15 @@ impl<'a, T, U> Server<'a, T, U> for IrcServer<'a, T, U> where T: IrcWriter, U: I self.conn.send(command.to_message()) } - fn iter(&'a self) -> ServerIterator<'a, T, U> { + fn iter(&'a self) -> ServerIterator<'a, T> { ServerIterator::new(self) } } -impl<'a, T, U> IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader { +impl<'a, T> IrcServer<'a, T> where T: IrcStream { /// Creates an IRC server from the specified configuration, and any arbitrary Connection. #[experimental] - pub fn from_connection(config: Config, conn: Connection) -> IrcServer<'a, T, U> { + pub fn from_connection(config: Config, conn: Connection) -> IrcServer<'a, T> { IrcServer { conn: conn, config: config @@ -79,7 +79,7 @@ impl<'a, T, U> IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader { } /// Gets a reference to the IRC server's connection. - pub fn conn(&self) -> &Connection { + pub fn conn(&self) -> &Connection { &self.conn } @@ -99,21 +99,21 @@ impl<'a, T, U> IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader { /// An Iterator over an IrcServer's incoming Messages. #[experimental] -pub struct ServerIterator<'a, T, U> where T: IrcWriter, U: IrcReader { - pub server: &'a IrcServer<'a, T, U> +pub struct ServerIterator<'a, T> where T: IrcStream { + pub server: &'a IrcServer<'a, T> } -impl<'a, T, U> ServerIterator<'a, T, U> where T: IrcWriter, U: IrcReader { +impl<'a, T> ServerIterator<'a, T> where T: IrcStream { /// Creates a new ServerIterator for the desired IrcServer. #[experimental] - pub fn new(server: &'a IrcServer<'a, T, U>) -> ServerIterator<'a, T, U> { + pub fn new(server: &'a IrcServer<'a, T>) -> ServerIterator<'a, T> { ServerIterator { server: server } } } -impl<'a, T, U> Iterator for ServerIterator<'a, T, U> where T: IrcWriter, U: IrcReader { +impl<'a, T> Iterator for ServerIterator<'a, T> where T: IrcStream { fn next(&mut self) -> Option { let line = self.server.conn.recv(); match line { @@ -133,7 +133,7 @@ mod test { use std::collections::HashMap; use std::io::{MemReader, MemWriter}; use std::io::util::{NullReader, NullWriter}; - use conn::Connection; + use conn::{Connection, IoStream}; use data::Config; use data::command::PRIVMSG; use data::kinds::IrcReader; @@ -153,15 +153,15 @@ mod test { } } - pub fn get_server_value(server: IrcServer) -> String where U: IrcReader { - String::from_utf8(server.conn().writer().get_ref().to_vec()).unwrap() + pub fn get_server_value(server: IrcServer>) -> String where U: IrcReader { + String::from_utf8(server.conn().stream().value()).unwrap() } #[test] fn iterator() { let exp = "PRIVMSG test :Hi!\r\nPRIVMSG test :This is a test!\r\n:test!test@test JOIN #test\r\n"; let server = IrcServer::from_connection(test_config(), - Connection::new(NullWriter, MemReader::new(exp.as_bytes().to_vec()))); + Connection::new(IoStream::new(NullWriter, MemReader::new(exp.as_bytes().to_vec())))); let mut messages = String::new(); for message in server.iter() { messages.push_str(message.into_string()[]); @@ -173,7 +173,7 @@ mod test { fn handle_message() { let value = "PING :irc.test.net\r\n:irc.test.net 376 test :End of /MOTD command.\r\n"; let server = IrcServer::from_connection(test_config(), - Connection::new(MemWriter::new(), MemReader::new(value.as_bytes().to_vec()))); + Connection::new(IoStream::new(MemWriter::new(), MemReader::new(value.as_bytes().to_vec())))); for message in server.iter() { println!("{}", message); } @@ -184,7 +184,7 @@ mod test { #[test] fn send() { let server = IrcServer::from_connection(test_config(), - Connection::new(MemWriter::new(), NullReader)); + Connection::new(IoStream::new(MemWriter::new(), NullReader))); assert!(server.send(PRIVMSG("#test", "Hi there!")).is_ok()); assert_eq!(get_server_value(server)[], "PRIVMSG #test :Hi there!\r\n"); diff --git a/src/server/utils.rs b/src/server/utils.rs index 356ad35..bd1df9b 100644 --- a/src/server/utils.rs +++ b/src/server/utils.rs @@ -5,16 +5,16 @@ use std::io::IoResult; use data::command::{Command, INVITE, JOIN, KILL, MODE, NICK, KICK}; use data::command::{OPER, PONG, PRIVMSG, SAMODE, SANICK, TOPIC, USER}; use data::config::Config; -use data::kinds::{IrcReader, IrcWriter}; +use data::kinds::IrcStream; use server::{Server, ServerIterator}; /// Functionality-providing wrapper for Server. #[experimental] -pub struct Wrapper<'a, T, U> where T: IrcWriter, U: IrcReader { - server: &'a Server<'a, T, U> + 'a +pub struct Wrapper<'a, T> where T: IrcStream { + server: &'a Server<'a, T> + 'a } -impl<'a, T, U> Server<'a, T, U> for Wrapper<'a, T, U> where T: IrcWriter, U: IrcReader { +impl<'a, T> Server<'a, T> for Wrapper<'a, T> where T: IrcStream { fn config(&self) -> &Config { self.server.config() } @@ -23,15 +23,15 @@ impl<'a, T, U> Server<'a, T, U> for Wrapper<'a, T, U> where T: IrcWriter, U: Irc self.server.send(command) } - fn iter(&'a self) -> ServerIterator<'a, T, U> { + fn iter(&'a self) -> ServerIterator<'a, T> { self.server.iter() } } -impl<'a, T, U> Wrapper<'a, T, U> where T: IrcWriter, U: IrcReader { +impl<'a, T> Wrapper<'a, T> where T: IrcStream { /// Creates a new Wrapper from the given Server. #[experimental] - pub fn new(server: &'a Server<'a, T, U>) -> Wrapper<'a, T, U> { + pub fn new(server: &'a Server<'a, T>) -> Wrapper<'a, T> { Wrapper { server: server } } @@ -137,14 +137,14 @@ mod test { use super::Wrapper; use std::io::MemWriter; use std::io::util::NullReader; - use conn::Connection; + use conn::{Connection, IoStream}; use server::IrcServer; use server::test::{get_server_value, test_config}; #[test] fn identify() { let server = IrcServer::from_connection(test_config(), - Connection::new(MemWriter::new(), NullReader)); + Connection::new(IoStream::new(MemWriter::new(), NullReader))); { let wrapper = Wrapper::new(&server); wrapper.identify().unwrap(); @@ -156,7 +156,7 @@ mod test { #[test] fn send_pong() { let server = IrcServer::from_connection(test_config(), - Connection::new(MemWriter::new(), NullReader)); + Connection::new(IoStream::new(MemWriter::new(), NullReader))); { let wrapper = Wrapper::new(&server); wrapper.send_pong("irc.test.net").unwrap(); @@ -168,7 +168,7 @@ mod test { #[test] fn send_join() { let server = IrcServer::from_connection(test_config(), - Connection::new(MemWriter::new(), NullReader)); + Connection::new(IoStream::new(MemWriter::new(), NullReader))); { let wrapper = Wrapper::new(&server); wrapper.send_join("#test,#test2,#test3").unwrap(); @@ -180,7 +180,7 @@ mod test { #[test] fn send_oper() { let server = IrcServer::from_connection(test_config(), - Connection::new(MemWriter::new(), NullReader)); + Connection::new(IoStream::new(MemWriter::new(), NullReader))); { let wrapper = Wrapper::new(&server); wrapper.send_oper("test", "test").unwrap(); @@ -192,7 +192,7 @@ mod test { #[test] fn send_privmsg() { let server = IrcServer::from_connection(test_config(), - Connection::new(MemWriter::new(), NullReader)); + Connection::new(IoStream::new(MemWriter::new(), NullReader))); { let wrapper = Wrapper::new(&server); wrapper.send_privmsg("#test", "Hi, everybody!").unwrap(); @@ -204,7 +204,7 @@ mod test { #[test] fn send_topic_no_topic() { let server = IrcServer::from_connection(test_config(), - Connection::new(MemWriter::new(), NullReader)); + Connection::new(IoStream::new(MemWriter::new(), NullReader))); { let wrapper = Wrapper::new(&server); wrapper.send_topic("#test", "").unwrap(); @@ -216,7 +216,7 @@ mod test { #[test] fn send_topic() { let server = IrcServer::from_connection(test_config(), - Connection::new(MemWriter::new(), NullReader)); + Connection::new(IoStream::new(MemWriter::new(), NullReader))); { let wrapper = Wrapper::new(&server); wrapper.send_topic("#test", "Testing stuff.").unwrap(); @@ -228,7 +228,7 @@ mod test { #[test] fn send_kill() { let server = IrcServer::from_connection(test_config(), - Connection::new(MemWriter::new(), NullReader)); + Connection::new(IoStream::new(MemWriter::new(), NullReader))); { let wrapper = Wrapper::new(&server); wrapper.send_kill("test", "Testing kills.").unwrap(); @@ -240,7 +240,7 @@ mod test { #[test] fn send_kick_no_message() { let server = IrcServer::from_connection(test_config(), - Connection::new(MemWriter::new(), NullReader)); + Connection::new(IoStream::new(MemWriter::new(), NullReader))); { let wrapper = Wrapper::new(&server); wrapper.send_kick("#test", "test", "").unwrap(); @@ -252,7 +252,7 @@ mod test { #[test] fn send_kick() { let server = IrcServer::from_connection(test_config(), - Connection::new(MemWriter::new(), NullReader)); + Connection::new(IoStream::new(MemWriter::new(), NullReader))); { let wrapper = Wrapper::new(&server); wrapper.send_kick("#test", "test", "Testing kicks.").unwrap(); @@ -264,7 +264,7 @@ mod test { #[test] fn send_mode_no_modeparams() { let server = IrcServer::from_connection(test_config(), - Connection::new(MemWriter::new(), NullReader)); + Connection::new(IoStream::new(MemWriter::new(), NullReader))); { let wrapper = Wrapper::new(&server); wrapper.send_mode("#test", "+i", "").unwrap(); @@ -276,7 +276,7 @@ mod test { #[test] fn send_mode() { let server = IrcServer::from_connection(test_config(), - Connection::new(MemWriter::new(), NullReader)); + Connection::new(IoStream::new(MemWriter::new(), NullReader))); { let wrapper = Wrapper::new(&server); wrapper.send_mode("#test", "+o", "test").unwrap(); @@ -288,7 +288,7 @@ mod test { #[test] fn send_samode_no_modeparams() { let server = IrcServer::from_connection(test_config(), - Connection::new(MemWriter::new(), NullReader)); + Connection::new(IoStream::new(MemWriter::new(), NullReader))); { let wrapper = Wrapper::new(&server); wrapper.send_samode("#test", "+i", "").unwrap(); @@ -300,7 +300,7 @@ mod test { #[test] fn send_samode() { let server = IrcServer::from_connection(test_config(), - Connection::new(MemWriter::new(), NullReader)); + Connection::new(IoStream::new(MemWriter::new(), NullReader))); { let wrapper = Wrapper::new(&server); wrapper.send_samode("#test", "+o", "test").unwrap(); @@ -312,7 +312,7 @@ mod test { #[test] fn send_sanick() { let server = IrcServer::from_connection(test_config(), - Connection::new(MemWriter::new(), NullReader)); + Connection::new(IoStream::new(MemWriter::new(), NullReader))); { let wrapper = Wrapper::new(&server); wrapper.send_sanick("test", "test2").unwrap(); @@ -324,7 +324,7 @@ mod test { #[test] fn send_invite() { let server = IrcServer::from_connection(test_config(), - Connection::new(MemWriter::new(), NullReader)); + Connection::new(IoStream::new(MemWriter::new(), NullReader))); { let wrapper = Wrapper::new(&server); wrapper.send_invite("test", "#test").unwrap();