//! Thread-safe connections on IrcStreams. #![experimental] use std::sync::{Mutex, MutexGuard}; use std::io::{BufferedStream, IoResult, MemWriter, TcpStream}; #[cfg(feature = "ssl")] use std::io::{IoError, OtherIoError}; 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: IrcStream { stream: Mutex } impl Connection> { /// Creates a thread-safe TCP connection to the specified server. #[experimental] pub fn connect(host: &str, port: u16) -> IoResult>> { let socket = try!(TcpStream::connect(format!("{}:{}", host, port)[])); Ok(Connection::new(BufferedStream::new(NetStream::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>> { let socket = try!(TcpStream::connect(format!("{}:{}", host, port)[])); let ssl = try!(ssl_to_io(SslContext::new(Tlsv1))); let ssl_socket = try!(ssl_to_io(SslStream::new(&ssl, socket))); Ok(Connection::new(BufferedStream::new(NetStream::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>> { 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 { /// An unsecured TcpStream. UnsecuredTcpStream(TcpStream), /// An SSL-secured TcpStream. /// This is only available when compiled with SSL support. #[cfg(feature = "ssl")] SslTcpStream(SslStream), } impl Reader for NetStream { fn read(&mut self, buf: &mut [u8]) -> IoResult { match self { &NetStream::UnsecuredTcpStream(ref mut stream) => stream.read(buf), #[cfg(feature = "ssl")] &NetStream::SslTcpStream(ref mut stream) => stream.read(buf), } } } impl Writer for NetStream { fn write(&mut self, buf: &[u8]) -> IoResult<()> { match self { &NetStream::UnsecuredTcpStream(ref mut stream) => stream.write(buf), #[cfg(feature = "ssl")] &NetStream::SslTcpStream(ref mut stream) => stream.write(buf), } } } impl Connection { /// Creates a new connection from any arbitrary IrcStream. #[experimental] pub fn new(stream: T) -> Connection { Connection { stream: Mutex::new(stream), } } /// Sends a Message over this connection. #[experimental] pub fn send(&self, message: Message) -> IoResult<()> { 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.stream.lock().read_line() } /// Acquires the Stream lock. #[experimental] 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, IoStream}; use std::io::{MemReader, MemWriter}; use std::io::util::{NullReader, NullWriter}; use data::message::Message; #[test] fn send() { 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.stream().value()).unwrap(); assert_eq!(data[], "PRIVMSG test :Testing!\r\n"); } #[test] fn recv() { 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"); } }